Resumer

侧边栏形式的个人简历助手、信息管理助手,支持自动填充、分类、搜索、拖拽排序等功能

// ==UserScript==
// @name         Resumer
// @namespace    https://gf.qytechs.cn/zh-CN/users/1375382-ryanli
// @version      4.0.6
// @description  侧边栏形式的个人简历助手、信息管理助手,支持自动填充、分类、搜索、拖拽排序等功能
// @author       Ryanli
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    /**
     * ----------------------------------------------------------------
     * 模块: Config
     * 存储所有常量、选择器、类名、存储键和魔法数字
     * ----------------------------------------------------------------
     */
    const Config = {
        SELECTORS: {
            ASSISTANT: '#personal-info-assistant',
            HEADER: '#assistant-header',
            TITLE: '#assistant-title',
            CONTROLS: '#assistant-controls',
            TOGGLE_BTN: '#toggle-btn',
            FIX_BTN: '#fix-btn',
            CLOSE_BTN: '#close-btn',
            CONTENT: '#assistant-content',
            CATEGORY_CONTAINER: '#category-container',
            CATEGORY_BTN: '.category-btn',
            ADD_CATEGORY_BTN: '#add-category',
            ITEMS_CONTAINER: '#items-container',
            INFO_ITEM: '.info-item',
            ITEM_TITLE: '.item-title',
            ITEM_CATEGORY: '.item-category',
            FOOTER: '#assistant-footer',
            SEARCH_INPUT: '#search-input',
            ADD_ITEM_BTN: '#add-item-btn',
            CONTEXT_MENU: '#context-menu',
            EDIT_ITEM_MENU: '#edit-item',
            DELETE_ITEM_MENU: '#delete-item',
            CATEGORY_CONTEXT_MENU: '#category-context-menu',
            RENAME_CATEGORY_MENU: '#rename-category',
            DELETE_CATEGORY_MENU: '#delete-category-menu',
            EDIT_MODAL: '#edit-modal',
            EDIT_TITLE: '#edit-title',
            EDIT_START_DATE: '#edit-start-date',
            EDIT_END_DATE: '#edit-end-date',
            EDIT_CONTENT: '#edit-content',
            EDIT_CATEGORY: '#edit-category',
            CANCEL_EDIT_BTN: '#cancel-edit',
            SAVE_EDIT_BTN: '#save-edit',
            CATEGORY_MODAL: '#category-modal',
            CATEGORY_NAME_INPUT: '#category-name',
            CANCEL_CATEGORY_BTN: '#cancel-category',
            SAVE_CATEGORY_BTN: '#save-category',
            OVERLAY: '#overlay',
            DETAIL_MODAL: '#detail-modal',
            DETAIL_TITLE: '#detail-title',
            DETAIL_CATEGORY: '#detail-category',
            DETAIL_DATE: '#detail-date',
            DETAIL_CONTENT: '#detail-content',
            // 动态创建的 Modal ID
            DELETE_ITEM_MODAL: '#delete-item-modal',
            CANCEL_DELETE_ITEM_BTN: '#cancel-delete-item',
            CONFIRM_DELETE_ITEM_BTN: '#confirm-delete-item',
            DELETE_CATEGORY_MODAL: '#delete-category-modal',
            CANCEL_DELETE_CATEGORY_BTN: '#cancel-delete-category',
            CONFIRM_DELETE_CATEGORY_BTN: '#confirm-delete-category',
            RENAME_CATEGORY_MODAL: '#rename-category-modal',
            NEW_CATEGORY_NAME_INPUT: '#new-category-name',
            CANCEL_RENAME_CATEGORY_BTN: '#cancel-rename-category',
            CONFIRM_RENAME_CATEGORY_BTN: '#confirm-rename-category',
            ALERT_MODAL: '#alert-modal', // 新增:通用提示框
        },
        CLASSES: {
            LEFT: 'left',
            COLLAPSED: 'collapsed',
            DRAGGING: 'dragging',
            DRAG_READY: 'drag-ready',
            FIXED: 'isFixed',
            OPEN: 'open',
            ACTIVE: 'active',
            CATEGORY_BTN: 'category-btn', // 修复:移除类名前的点
            INFO_ITEM: 'info-item', // 修复:添加 info-item 类名
            DYNAMIC_MODAL: 'assistant-dynamic-modal', // 动态模态框的通用类
        },
        STORAGE_KEYS: {
            RESET_FLAG: 'personalInfoAssistant_resetStorage',
            CATEGORIES: 'personalInfoAssistant_categories',
            ITEMS: 'personalInfoAssistant_items',
            IS_FIXED: 'personalInfoAssistant_isFixed',
            SIDEBAR_POSITION: 'personalInfoAssistant_sidebarPosition',
            COLLAPSED_POSITION: 'personalInfoAssistant_collapsedPosition',
            OLD_DATA: 'personalInfoAssistantData', // 兼容旧版
        },
        TIMERS: {
            SIDEBAR_DRAG_START_DELAY: 300,  // 侧边栏拖拽延迟
            DETAIL_HOVER_DELAY: 1000,        // 详情悬停
            AUTO_FILL_TIMEOUT: 3000,         // 自动填充
            TINYMCE_POLL_INTERVAL: 200,    // TinyMCE 轮询间隔
            TINYMCE_POLL_TIMEOUT: 2000,        // TinyMCE 轮询超时
            SIMULATE_INPUT_INTERVAL: 300,    // 模拟输入检查间隔
            SIMULATE_INPUT_TIMEOUT: 3000,        // 模拟输入超时
        },
        DEFAULT_CATEGORY: '全部',
    };

    /**
     * ----------------------------------------------------------------
     * 模块: StyleManager
     * 负责管理和注入所有 CSS 样式
     * ----------------------------------------------------------------
     */
    const StyleManager = {
        inject: function() {
            GM_addStyle(this.getStyles());
            this.addAntiSelectStyles();
        },

        addAntiSelectStyles: function() {
            const style = document.createElement('style');
            style.textContent = `
                /* 彻底禁用整个侧边栏的文本选择 */
                ${Config.SELECTORS.ASSISTANT},
                ${Config.SELECTORS.ASSISTANT} *,
                ${Config.SELECTORS.ASSISTANT} *:before,
                ${Config.SELECTORS.ASSISTANT} *:after {
                    user-select: none !important;
                    -webkit-user-select: none !important;
                    -moz-user-select: none !important;
                    -ms-user-select: none !important;
                    -webkit-touch-callout: none !important;
                    -webkit-tap-highlight-color: transparent !important;
                    cursor: default !important;
                }

                /* 特殊处理按钮和可点击元素 */
                ${Config.SELECTORS.ASSISTANT} button,
                ${Config.SELECTORS.INFO_ITEM},
                ${Config.SELECTORS.CATEGORY_BTN} {
                    cursor: pointer !important;
                }
                
                /* 屏蔽整个侧边栏的右键菜单 */
                ${Config.SELECTORS.ASSISTANT},
                ${Config.SELECTORS.ASSISTANT} * {
                    -webkit-touch-callout: none !important;
                    -webkit-user-select: none !important;
                    -khtml-user-select: none !important;
                    -moz-user-select: none !important;
                    -ms-user-select: none !important;
                    user-select: none !important;
                }
            `;
            document.head.appendChild(style);
        },

        getStyles: function() {
            // (样式字符串过长,此处折叠)
            return `
        #personal-info-assistant {
            position: fixed;
            top: 0;
            right: 0;
            width: 300px;
            height: 100vh;
            background: #f5f5f5;
            border-left: 1px solid #ddd;
            box-shadow: -4px 0 20px rgba(0,0,0,0.08);
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            z-index: 9999;
            display: flex;
            flex-direction: column;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            border-radius: 16px 0 0 16px; /* 左侧大圆角,右侧贴边不留 */
            overflow: hidden;
        }
        #personal-info-assistant.left {
            right: auto;
            left: 0;
            border-left: none;
            border-right: 1px solid #ddd;
            border-radius: 0 16px 16px 0;
            box-shadow: 4px 0 20px rgba(0,0,0,0.08);
        }
        #personal-info-assistant.collapsed {
            width: 48px;
            height: auto;
            top: var(--collapsed-top, 45%);
            transform: translateY(-50%);
            border-radius: 16px;
            box-shadow: 0 6px 24px rgba(0,0,0,0.12);
            cursor: pointer;
            /* 优化拖拽动画性能 */
            will-change: top;
            transition: top 0.1s ease-out;
        }
        
        /* 拖拽过程中的流畅动画 */
        #personal-info-assistant.collapsed.dragging {
            transition: none; /* 拖拽时禁用过渡动画 */
            box-shadow: 0 8px 32px rgba(0,0,0,0.16); /* 拖拽时增强阴影 */
            transform: translateY(-50%) scale(1.02); /* 轻微放大效果 */
            background: linear-gradient(135deg, #4CAF50, #45a049); /* 拖拽时改变背景色 */
        }
        
        /* 拖拽准备状态(长按计时器期间) */
        #personal-info-assistant.collapsed.drag-ready {
            background: linear-gradient(135deg, #FF9800, #F57C00); /* 橙色表示准备拖拽 */
            box-shadow: 0 6px 24px rgba(255, 152, 0, 0.3);
        }
        #personal-info-assistant.collapsed #assistant-content,
        #personal-info-assistant.collapsed #assistant-footer {
            display: none;
        }
        /* 固定状态 - 保持固定定位不随页面滚动 */
        #personal-info-assistant.fixed {
            position: fixed;
            z-index: 10000;
        }
        #assistant-header {
            padding: 15px;
            background: linear-gradient(135deg, #4CAF50, #45a049);
            color: white;
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-radius: 16px 0 0 0; /* 顶部圆角与主容器一致 */
        }
        
        /* 面板最小化时的通用样式 */
        #personal-info-assistant.collapsed #assistant-header {
            justify-content: center;
            position: relative;
        }
        
        #personal-info-assistant.collapsed #assistant-title {
            text-align: center;
            position: relative;
            z-index: 2;
        }
        
        /* 右侧最小化时的按钮位置 - 紧贴左侧边缘 */
        #personal-info-assistant.collapsed:not(.left) #assistant-controls {
            position: absolute;
            right: 0;
            top: 50%;
            transform: translateY(-50%);
            width: 16px;
            display: flex;
            justify-content: center;
            z-index: 1;
        }
        
        /* 左侧最小化时的按钮位置 - 紧贴右侧边缘 */
        #personal-info-assistant.collapsed.left #assistant-controls {
            position: absolute;
            left: 0;
            top: 50%;
            transform: translateY(-50%);
            width: 16px;
            display: flex;
            justify-content: center;
            z-index: 1;
        }
        #personal-info-assistant.collapsed #fix-btn,
        #personal-info-assistant.collapsed #close-btn {
            display: none;
        }
        #assistant-title {
            font-size: 16px;
            font-weight: bold;
            cursor: pointer;
            user-select: none;
        }
        #assistant-controls {
            display: flex;
            gap: 8px;
        }
        .control-btn {
            width: 24px;
            height: 24px;
            border: none;
            background: rgba(255,255,255,0.2);
            color: white;
            cursor: pointer;
            border-radius: 6px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 12px;
            font-weight: bold;
            transition: all 0.3s ease;
            user-select: none;
        }
        .control-btn:hover {
            background: rgba(255,255,255,0.4);
            transform: translateY(-1px);
            box-shadow: 0 3px 10px rgba(255,255,255,0.3);
        }
        .control-btn:active {
            transform: translateY(0);
            box-shadow: 0 1px 3px rgba(255,255,255,0.3);
        }
        #assistant-content {
            flex: 1;
            display: flex;
            overflow: hidden;
        }
        #category-container {
            width: 80px;
            background: #e8e8e8;
            overflow-y: auto;
            overflow-x: hidden;
            padding: 10px 0;
            border-right: 1px solid #ddd;
        }
        .category-btn {
            width: 100%;
            padding: 12px 8px;
            border: none;
            background: transparent;
            cursor: pointer;
            font-size: 12px;
            text-align: center;
            word-break: break-all;
            position: relative;
            transition: all 0.3s ease;
            border-radius: 8px;
            color: #4a4a4a;
            margin-bottom: 4px;
            user-select: none;
        }
        .category-btn.active {
            background: linear-gradient(135deg, #4CAF50, #45a049);
            color: white;
            font-weight: bold;
            box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
        }
        .category-btn:hover:not(.active) {
            background: #f0f0f0;
            transform: translateX(2px);
            color: #333;
        }
        
        /* 分类拖拽相关样式 */
        .category-btn.dragging {
            opacity: 0.5;
            border: 2px dashed #666;
        }
        .category-btn .delete-category {
            position: absolute;
            top: 2px;
            right: 2px;
            width: 12px;
            height: 12px;
            background: #ff6b6b;
            color: white;
            border: none;
            border-radius: 50%;
            cursor: pointer;
            font-size: 8px;
            display: none;
            align-items: center;
            justify-content: center;
        }
        .category-btn:hover .delete-category {
            display: flex;
        }
        #add-category {
            width: 100%;
            padding: 5px;
            border: none;
            background: transparent;
            cursor: pointer;
            font-size: 20px;
            color: #666;
            user-select: none;
        }
        #items-container {
            flex: 1;
            overflow-y: auto;
            padding: 10px;
        }
        .info-item {
            background: white;
            border: 1px solid #e0e0e0;
            border-radius: 12px;
            padding: 12px 15px 10px;
            margin-bottom: 12px;
            cursor: pointer;
            position: relative;
            /* 全面禁止文本选择的CSS属性 */
            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
            /* 禁用拖动选择和长按菜单 */
            -webkit-touch-callout: none;
            -webkit-tap-highlight-color: transparent;
            transition: all 0.3s ease;
            transform: translateY(0);
            overflow: hidden;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
        }
        .info-item::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 4px;
            background: linear-gradient(90deg, #4CAF50, #45a049);
            transform: scaleX(0);
            transition: transform 0.3s ease;
        }
        .info-item:hover {
            box-shadow: 0 8px 20px rgba(0,0,0,0.12);
            transform: translateY(-3px);
            border-color: #d0d0d0;
        }
        .info-item:hover::before {
            transform: scaleX(1);
        }
        .info-item.dragging {
            opacity: 0.5;
            border: 2px dashed #666;
        }
        .item-title {
            font-weight: bold;
            color: #333;
            margin-bottom: 2px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            font-size: 15px;
            width: 100%;
            display: block;
            text-align: center;
            user-select: none;
        }
        .item-date {
            font-size: 11px;
            color: #666;
            margin-top: 2px;
            text-align: center;
            display: block;
        }
        .item-category {
            font-size: 11px;
            color: #666;
            background: rgba(240, 240, 240, 0.8);
            padding: 2px 6px;
            border-radius: 4px;
            margin-left: auto;
            margin-bottom: 1px;
            display: inline-block;
            margin-top: -5px;
            user-select: none;
        }
        .info-item-header {
            display: flex;
            justify-content: flex-end;
            align-items: flex-start;
            margin-bottom: -3px;
            width: 100%;
        }
        #assistant-footer {
            padding: 15px;
            background: #e8e8e8;
            border-top: 1px solid #ddd;
        }
        #search-input {
            width: 100%;
            padding: 12px 16px;
            border: 2px solid #e0e0e0;
            border-radius: 12px;
            font-size: 14px;
            box-sizing: border-box;
            transition: all 0.3s ease;
            background: white;
            outline: none;
        }
        #search-input:focus {
            border-color: #4CAF50;
            box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
        }
        #add-item-btn {
            width: 100%;
            padding: 12px 16px;
            margin-top: 12px;
            border: none;
            background: linear-gradient(135deg, #4CAF50, #45a049);
            color: white;
            border-radius: 12px;
            cursor: pointer;
            font-size: 15px;
            font-weight: 500;
            transition: all 0.3s ease;
            box-shadow: 0 4px 16px rgba(76, 175, 80, 0.3);
        }
        #add-item-btn:hover {
            background: linear-gradient(135deg, #45a049, #3d8b40);
            transform: translateY(-2px);
            box-shadow: 0 8px 24px rgba(76, 175, 80, 0.4);
        }
        #add-item-btn:active {
            transform: translateY(0);
            box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
        }
        .context-menu {
            position: fixed;
            background: white;
            border: 1px solid #ddd;
            border-radius: 8px;
            box-shadow: 0 4px 16px rgba(0,0,0,0.15);
            padding: 8px 0;
            display: none;
            z-index: 10000;
            min-width: 120px;
        }
        .context-menu-item {
            padding: 10px 18px;
            cursor: pointer;
            font-size: 14px;
            transition: background-color 0.2s ease;
            user-select: none;
        }
        .context-menu-item:hover {
            background: #f0f0f0;
        }
        #edit-modal {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            border-radius: 16px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.25);
            padding: 25px;
            width: 90%;
            max-width: 450px;
            display: none;
            z-index: 10000;
            border: 1px solid #e0e0e0;
        }
        .modal-title {
            font-size: 20px;
            font-weight: bold;
            margin-bottom: 20px;
            text-align: center;
            color: #333;
            user-select: none;
        }
        .form-group {
            margin-bottom: 18px;
        }
        .form-group label {
            display: block;
            margin-bottom: 8px;
            font-weight: bold;
            font-size: 14px;
            color: #444;
            user-select: none;
        }
        .form-group input,
        .form-group textarea,
        .form-group select {
            width: 100%;
            padding: 10px 12px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 14px;
            box-sizing: border-box;
            transition: border-color 0.3s ease;
        }
        .form-group input:focus,
        .form-group textarea:focus,
        .form-group select:focus {
            outline: none;
            border-color: #4CAF50;
            box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
        }
        .form-group textarea {
            height: 100px;
            resize: vertical;
        }
        .modal-actions {
            display: flex;
            gap: 12px;
            justify-content: flex-end;
            margin-top: 25px;
        }
        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 500;
            transition: all 0.3s ease;
            user-select: none;
        }
        .btn-primary {
            background: linear-gradient(135deg, #4CAF50, #45a049);
            color: white;
            box-shadow: 0 2px 8px rgba(76, 175, 80, 0.3);
        }
        .btn-primary:hover {
            background: linear-gradient(135deg, #45a049, #3d8b40);
            transform: translateY(-1px);
            box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4);
        }
        .btn-primary:active {
            transform: translateY(0);
        }
        .btn-secondary {
            background: #f5f5f5;
            color: #333;
            border: 1px solid #ddd;
        }
        .btn-secondary:hover {
            background: #e0e0e0;
            transform: translateY(-1px);
        }
        .btn-secondary:active {
            transform: translateY(0);
        }
        #overlay {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0,0,0,0.5);
            backdrop-filter: blur(4px);
            display: none;
            z-index: 9998;
        }
        /* 详情弹窗样式 - 基于编辑弹窗样式但不包含操作按钮 */
        #detail-modal {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            border-radius: 16px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.25);
            padding: 25px;
            width: 90%;
            max-width: 450px;
            display: none;
            z-index: 10000;
            border: 1px solid #e0e0e0;
        }
        
        #detail-modal .modal-title {
            font-size: 20px;
            font-weight: bold;
            margin-bottom: 20px;
            text-align: center;
            color: #333;
        }
        
        #detail-modal .info-field {
            margin-bottom: 18px;
        }
        
        #detail-modal .field-label {
            display: block;
            margin-bottom: 8px;
            font-weight: bold;
            font-size: 14px;
            color: #444;
        }
        
        #detail-modal .field-value {
            padding: 10px 12px;
            background: #f8f8f8;
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            font-size: 14px;
            box-sizing: border-box;
            word-wrap: break-word;
            white-space: pre-wrap;
        }
        
        #detail-modal .field-value.content {
            min-height: 100px;
            line-height: 1.5;
        }
        /* 动态创建的模态框的通用样式 */
        .assistant-dynamic-modal {
            position: fixed; 
            top: 50%; 
            left: 50%; 
            transform: translate(-50%, -50%); 
            background: white; 
            border-radius: 16px; 
            box-shadow: 0 10px 30px rgba(0,0,0,0.25); 
            padding: 25px; 
            width: 90%; 
            max-width: 450px; 
            display: none; 
            z-index: 10000; 
            border: 1px solid #e0e0e0;
        }
            `;
        }
    };

    /**
     * ----------------------------------------------------------------
     * 模块: Utils
     * 存放可复用的工具函数
     * ----------------------------------------------------------------
     */
    const Utils = {
        generateId: function() {
            return Date.now().toString(36) + Math.random().toString(36).substr(2);
        },

        copyToClipboard: function(text) {
            navigator.clipboard.writeText(text).then(() => {
                console.log('[Clipboard Debug] 成功复制到剪贴板:', text);
            }).catch(err => {
                console.error('[Clipboard Debug] 复制失败:', err);
            });
        },

        simulateInputAtCursor: function(message) {
            const maxWaitTime = Config.TIMERS.SIMULATE_INPUT_TIMEOUT;
            const checkInterval = Config.TIMERS.SIMULATE_INPUT_INTERVAL;
            let attempts = 0;

            const interval = setInterval(() => {
                const activeElement = document.activeElement;

                if (activeElement && (
                    activeElement instanceof HTMLInputElement ||
                    activeElement instanceof HTMLTextAreaElement ||
                    (activeElement.isContentEditable && activeElement.contentEditable === 'true')
                )) {
                    clearInterval(interval);
                    activeElement.focus();

                    // 方式一:尝试使用 document.execCommand 插入文本
                    if (document.queryCommandSupported && document.queryCommandSupported('insertText')) {
                        try {
                            document.execCommand('insertText', false, message);
                            console.log('粘贴成功(方式一:execCommand)');
                            return;
                        } catch (e) {
                            console.warn('方式一失败,尝试其他方法');
                        }
                    }

                    // 方式二:现代API setRangeText(仅限 input/textarea)
                    if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {
                        if (typeof activeElement.setRangeText === 'function') {
                            try {
                                activeElement.setRangeText(
                                    message,
                                    activeElement.selectionStart,
                                    activeElement.selectionEnd,
                                    'end'
                                );
                                console.log('粘贴成功(方式二:setRangeText)');
                                return;
                            } catch (e) {
                                console.warn('方式二失败');
                            }
                        }
                    }

                    // 方式三:contenteditable 的 Selection + Range API
                    if (activeElement.isContentEditable) {
                        try {
                            const sel = window.getSelection();
                            if (sel.rangeCount > 0) {
                                const range = sel.getRangeAt(0);
                                range.deleteContents();
                                range.insertNode(document.createTextNode(message));
                                range.collapse(false);
                                console.log('粘贴成功(方式三:Range API)');
                                return;
                            }
                        } catch (e) {
                            console.error('方式三失败');
                        }
                    }
                    
                    // 备用方法: 模拟粘贴事件
                    try {
                        const clipboardData = new DataTransfer();
                        clipboardData.setData('text/plain', message);
                        const pasteEvent = new ClipboardEvent('paste', {
                            bubbles: true,
                            cancelable: true,
                            clipboardData
                        });
                        activeElement.dispatchEvent(pasteEvent);
                        console.log("尝试模拟触发粘贴事件");
                        return;
                    } catch(e) {
                         console.error('模拟粘贴事件失败');
                    }

                } else {
                    attempts++;
                    if (attempts * checkInterval >= maxWaitTime) {
                        clearInterval(interval);
                        console.error(`在${maxWaitTime / 1000}秒内未找到可输入的焦点元素,放弃执行粘贴动作。`);
                    }
                }
            }, checkInterval);
        }
    };

    /**
     * ----------------------------------------------------------------
     * 模块: State
     * 管理应用所有内存中的动态数据
     * ----------------------------------------------------------------
     */
    const State = {
        // 持久化数据
        data: {
            isFixed: true,
            sidebarPosition: 'right', // 'left' or 'right'
            collapsedPosition: null,  // { top: number }
            categories: ['工作', '学习', '生活'],
            items: [
                {
                    title: '示例信息',
                    content: '这是一条示例信息,您可以编辑或删除它。',
                    category: '工作',
                    startDate: '2025-10-03',
                    endDate: '2025-10-03',
                    id: Utils.generateId(),
                    order: 1
                }
            ],
            activeCategory: Config.DEFAULT_CATEGORY,
        },
        // 临时 UI 状态
        ui: {
            isExpanded: false,
            isDraggingSidebar: false,
            sidebarDragStartY: 0,
            sidebarDragStartTop: 0,
            sidebarLongPressTimer: null,
            hasDraggedSidebar: false,
            lastSidebarDragY: 0,
            sidebarDragVelocity: 0,
            lastSidebarDragTime: 0,
            lastSidebarAnimationFrame: null,
        },
        // 自动填充状态
        autofill: {
            lastClickedItemContent: null,
            timeout: null,
        },
        // 详情悬停状态
        hoverDetail: {
            timer: null,
            isMouseOver: false,
            currentItem: null,
        },
        // 拖拽状态
        drag: {
            draggedItem: null,
            overItem: null,
            draggedCategory: null,
            overCategory: null,
        }
    };

    /**
     * ----------------------------------------------------------------
     * 模块: Storage (Model)
     * 封装 GM API,负责数据的持久化、加载和 CRUD 操作
     * ----------------------------------------------------------------
     */
    const Storage = {
        initialize: function() {
            const resetFlag = GM_getValue(Config.STORAGE_KEYS.RESET_FLAG, true);
            if (resetFlag) {
                GM_setValue(Config.STORAGE_KEYS.RESET_FLAG, false);
                // 使用 State 中的默认数据
                GM_setValue(Config.STORAGE_KEYS.CATEGORIES, State.data.categories);
                GM_setValue(Config.STORAGE_KEYS.ITEMS, State.data.items);
                GM_setValue(Config.STORAGE_KEYS.IS_FIXED, State.data.isFixed);
                GM_setValue(Config.STORAGE_KEYS.SIDEBAR_POSITION, State.data.sidebarPosition);
                GM_setValue(Config.STORAGE_KEYS.COLLAPSED_POSITION, State.data.collapsedPosition);
                console.log('个人信息助手存储结构已初始化');
            }
        },

        load: function() {
            // 兼容旧版
            const oldFormatData = GM_getValue(Config.STORAGE_KEYS.OLD_DATA, null);
            if (oldFormatData) {
                try {
                    const parsedData = JSON.parse(oldFormatData);
                    State.data = { ...State.data, ...parsedData };
                    this.save(); // 迁移到新格式
                    GM_setValue(Config.STORAGE_KEYS.OLD_DATA, null); // 删除旧数据
                    console.error('数据已从旧格式迁移到新格式');
                    return;
                } catch (e) {
                    console.error('Failed to parse old format data:', e);
                }
            }

            // 加载新格式数据
            const categories = GM_getValue(Config.STORAGE_KEYS.CATEGORIES, null);
            const items = GM_getValue(Config.STORAGE_KEYS.ITEMS, null);
            const isFixed = GM_getValue(Config.STORAGE_KEYS.IS_FIXED, null);
            const sidebarPosition = GM_getValue(Config.STORAGE_KEYS.SIDEBAR_POSITION, null);
            const collapsedPosition = GM_getValue(Config.STORAGE_KEYS.COLLAPSED_POSITION, null);

            if (categories) State.data.categories = categories;
            if (items) State.data.items = items;
            if (isFixed !== null) State.data.isFixed = isFixed;
            if (sidebarPosition) State.data.sidebarPosition = sidebarPosition;
            if (collapsedPosition) State.data.collapsedPosition = collapsedPosition;
        },

        save: function() {
            // 格式化 items 确保数据一致性
            const formattedItems = State.data.items.map(item => ({
                title: item.title || '',
                content: item.content || '',
                category: item.category || Config.DEFAULT_CATEGORY,
                startDate: item.startDate || '',
                endDate: item.endDate || '',
                id: item.id || Utils.generateId(),
                order: item.order || 1
            }));
            State.data.items = formattedItems;

            GM_setValue(Config.STORAGE_KEYS.CATEGORIES, State.data.categories);
            GM_setValue(Config.STORAGE_KEYS.ITEMS, State.data.items);
            GM_setValue(Config.STORAGE_KEYS.IS_FIXED, State.data.isFixed);
            GM_setValue(Config.STORAGE_KEYS.SIDEBAR_POSITION, State.data.sidebarPosition);
            GM_setValue(Config.STORAGE_KEYS.COLLAPSED_POSITION, State.data.collapsedPosition);
        },

        saveItem: function(itemData) {
            if (itemData.id) {
                // 编辑
                const index = State.data.items.findIndex(i => i.id === itemData.id);
                if (index !== -1) {
                    State.data.items[index] = { ...State.data.items[index], ...itemData };
                }
            } else {
                // 新增
                const maxOrder = State.data.items.length > 0 ? Math.max(...State.data.items.map(i => i.order)) : 0;
                State.data.items.push({
                    ...itemData,
                    id: Utils.generateId(),
                    order: maxOrder + 1
                });
            }
            this.save();
        },

        deleteItemById: function(itemId) {
            State.data.items = State.data.items.filter(item => item.id !== itemId);
            this.save();
        },

        saveCategory: function(categoryName) {
            if (categoryName && !State.data.categories.includes(categoryName)) {
                State.data.categories.push(categoryName);
                this.save();
                return true;
            }
            return false;
        },

        deleteCategory: function(categoryName, moveItems) {
            if (moveItems) {
                // 移动项目到“全部”
                State.data.items = State.data.items.map(item => {
                    if (item.category === categoryName) {
                        return { ...item, category: Config.DEFAULT_CATEGORY };
                    }
                    return item;
                });
            } else {
                // 删除分类下的所有项目
                State.data.items = State.data.items.filter(item => item.category !== categoryName);
            }
            // 删除分类
            State.data.categories = State.data.categories.filter(cat => cat !== categoryName);
            this.save();
        },

        renameCategory: function(oldName, newName) {
            if (!newName || newName === oldName) return false;
            if (State.data.categories.includes(newName)) {
                UI.showAlertModal('该分类名称已存在');
                return false;
            }

            // 更新分类数组
            const index = State.data.categories.indexOf(oldName);
            if (index !== -1) {
                State.data.categories[index] = newName;
            }

            // 更新所有相关条目
            State.data.items = State.data.items.map(item => {
                if (item.category === oldName) {
                    return { ...item, category: newName };
                }
                return item;
            });

            this.save();
            return true;
        },

        updateItemOrder: function(draggedId, targetId) {
            const items = State.data.items;
            const draggedItem = items.find(item => item.id === draggedId);
            const targetItem = items.find(item => item.id === targetId);

            if (draggedItem && targetItem) {
                items.sort((a, b) => a.order - b.order);
                const draggedIdx = items.findIndex(item => item.id === draggedId);
                let targetIdx = items.findIndex(item => item.id === targetId);

                if (draggedIdx !== -1 && targetIdx !== -1) {
                    const [removedItem] = items.splice(draggedIdx, 1);
                    // 调整目标索引
                    targetIdx = items.findIndex(item => item.id === targetId);
                    items.splice(targetIdx, 0, removedItem);
                    
                    // 重新分配 order
                    items.forEach((item, index) => {
                        item.order = index + 1;
                    });
                    this.save();
                }
            }
        },

        updateCategoryOrder: function(draggedCategory, targetCategory) {
            if (draggedCategory === Config.DEFAULT_CATEGORY || targetCategory === Config.DEFAULT_CATEGORY) {
                return;
            }
            
            const categories = State.data.categories;
            const draggedIdx = categories.indexOf(draggedCategory);
            const targetIdx = categories.indexOf(targetCategory);

            if (draggedIdx !== -1 && targetIdx !== -1) {
                const [removedCategory] = categories.splice(draggedIdx, 1);
                categories.splice(targetIdx, 0, removedCategory);
                this.save();
            }
        }
    };

    /**
     * ----------------------------------------------------------------
     * 模块: DOM (View)
     * 负责 DOM 的创建、缓存和渲染
     * ----------------------------------------------------------------
     */
    const DOM = {
        elements: {}, // 缓存 DOM 元素

        create: function() {
            const assistant = document.createElement('div');
            assistant.id = 'personal-info-assistant';
            assistant.className = Config.CLASSES.COLLAPSED; // 默认收起

            const isLeftSide = State.data.sidebarPosition === 'left';
            const toggleBtnText = isLeftSide ? '▶' : '◀';
            const toggleBtnTitle = isLeftSide ? '移到右侧' : '移到左侧';

            assistant.innerHTML = `
                <div id="assistant-header">
                    <div id="assistant-title">信息助手</div>
                    <div id="assistant-controls">
                        <button class="control-btn" id="toggle-btn" title="${toggleBtnTitle}">${toggleBtnText}</button>
                        <button class="control-btn" id="fix-btn" title="固定">🔒</button>
                        <button class="control-btn" id="close-btn" title="点击关闭侧边栏">×</button>
                    </div>
                </div>
                <div id="assistant-content">
                    <div id="category-container"></div>
                    <div id="items-container"></div>
                </div>
                <div id="assistant-footer">
                    <input type="text" id="search-input" placeholder="搜索...">
                    <button id="add-item-btn">+ 添加信息</button>
                </div>
            `;

            const itemContextMenu = document.createElement('div');
            itemContextMenu.className = 'context-menu';
            itemContextMenu.id = 'context-menu';
            itemContextMenu.innerHTML = `
                <div class="context-menu-item" id="edit-item">编辑</div>
                <div class="context-menu-item" id="delete-item">删除</div>
            `;

            const categoryContextMenu = document.createElement('div');
            categoryContextMenu.className = 'context-menu';
            categoryContextMenu.id = 'category-context-menu';
            categoryContextMenu.innerHTML = `
                <div class="context-menu-item" id="rename-category">重命名</div>
                <div class="context-menu-item" id="delete-category-menu">删除</div>
            `;

            const editModal = document.createElement('div');
            editModal.id = 'edit-modal';
            editModal.innerHTML = `
                <div class="modal-title">编辑信息</div>
                <div class="form-group">
                    <label for="edit-title">标题</label>
                    <input type="text" id="edit-title" required>
                </div>
                <div class="form-group">
                    <label for="edit-start-date">开始日期</label>
                    <input type="date" id="edit-start-date">
                </div>
                <div class="form-group">
                    <label for="edit-end-date">结束日期</label>
                    <input type="date" id="edit-end-date">
                </div>
                <div class="form-group">
                    <label for="edit-content">内容</label>
                    <textarea id="edit-content" required></textarea>
                </div>
                <div class="form-group">
                    <label for="edit-category">分类</label>
                    <select id="edit-category"></select>
                </div>
                <div class="modal-actions">
                    <button class="btn btn-secondary" id="cancel-edit">取消</button>
                    <button class="btn btn-primary" id="save-edit">保存</button>
            </div>
            `;

            const categoryModal = document.createElement('div');
            categoryModal.id = 'category-modal';
            categoryModal.className = Config.CLASSES.DYNAMIC_MODAL; // 使用通用类
            categoryModal.style.display = 'none'; // 确保默认隐藏
            categoryModal.innerHTML = `
                <div class="modal-title">添加分类</div>
                <div class="form-group">
                    <label for="category-name">分类名称</label>
                    <input type="text" id="category-name" placeholder="请输入分类名称" required>
                </div>
                <div class="modal-actions">
                    <button class="btn btn-secondary" id="cancel-category">取消</button>
                    <button class="btn btn-primary" id="save-category">确定</button>
            </div>
            `;
            
            // --- 新增:创建所有“动态”模态框 ---

            const detailModal = document.createElement("div");
            detailModal.id = Config.SELECTORS.DETAIL_MODAL.slice(1);
            detailModal.className = Config.CLASSES.DYNAMIC_MODAL; // 使用通用类
            detailModal.innerHTML = `
                <div class="modal-title" id="detail-title"></div>
                <div class="info-field">
                    <div class="field-label">分类</div>
                    <div class="field-value" id="detail-category"></div>
                </div>
                <div class="info-field">
                    <div class="field-label">日期范围</div>
                    <div class="field-value" id="detail-date"></div>
                </div>
                <div class="info-field">
                    <div class="field-label">内容</div>
                    <div class="field-value content" id="detail-content"></div>
                </div>
            `;

            const deleteItemModal = document.createElement('div');
            deleteItemModal.id = Config.SELECTORS.DELETE_ITEM_MODAL.slice(1);
            deleteItemModal.className = Config.CLASSES.DYNAMIC_MODAL; // 使用通用类
            deleteItemModal.innerHTML = `
                <div class="modal-title">删除信息</div>
                <div style="margin: 20px 0; font-size: 14px; color: #333;">
                    确定要删除这条信息吗?此操作无法撤销。
                </div>
                <div class="modal-actions">
                    <button class="btn btn-secondary" id="cancel-delete-item">取消</button>
                    <button class="btn btn-primary" id="confirm-delete-item">确定</button>
                </div>
            `;

            const deleteCategoryModal = document.createElement('div');
            deleteCategoryModal.id = Config.SELECTORS.DELETE_CATEGORY_MODAL.slice(1);
            deleteCategoryModal.className = Config.CLASSES.DYNAMIC_MODAL; // 使用通用类
            deleteCategoryModal.innerHTML = `
                <div class="modal-title">删除分类</div>
                <div class="delete-category-message" style="margin: 20px 0; font-size: 14px; color: #333;"></div>
                <div style="margin-bottom: 20px; display: flex; gap: 12px; flex-direction: column;">
                    <label style="display: flex; align-items: center; cursor: pointer;">
                        <input type="radio" name="delete-option" value="move" checked style="margin-right: 8px;">
                        <span>将该分类下的所有信息移动到“<strong>${Config.DEFAULT_CATEGORY}</strong>”</span>
                    </label>
                    <label style="display: flex; align-items: center; cursor: pointer;">
                        <input type="radio" name="delete-option" value="delete" style="margin-right: 8px;">
                        <span>直接删除该分类下的所有信息</span>
                    </label>
                </div>
                <div class="modal-actions">
                    <button class="btn btn-secondary" id="cancel-delete-category">取消</button>
                    <button class="btn btn-primary" id="confirm-delete-category">确定</button>
                </div>
            `;

            const renameCategoryModal = document.createElement('div');
            renameCategoryModal.id = Config.SELECTORS.RENAME_CATEGORY_MODAL.slice(1);
            renameCategoryModal.className = Config.CLASSES.DYNAMIC_MODAL; // 使用通用类
            renameCategoryModal.innerHTML = `
                <div class="modal-title">重命名分类</div>
                <div class="rename-category-message" style="margin: 20px 0; font-size: 14px; color: #333;"></div>
                <div class="form-group">
                    <label for="new-category-name">新分类名称</label>
                    <input type="text" id="new-category-name" required>
                </div>
                <div class="modal-actions">
                    <button class="btn btn-secondary" id="cancel-rename-category">取消</button>
                    <button class="btn btn-primary" id="confirm-rename-category">确定</button>
                </div>
            `;

            const alertModal = document.createElement('div');
            alertModal.id = Config.SELECTORS.ALERT_MODAL.slice(1);
            alertModal.className = Config.CLASSES.DYNAMIC_MODAL;
            alertModal.innerHTML = `
                <div class="modal-title" id="alert-modal-title">提示</div>
                <div class="alert-message" style="margin: 20px 0; font-size: 14px; color: #333; text-align: center;"></div>
                <div class="modal-actions" style="justify-content: center;">
                    <button class="btn btn-primary" id="alert-modal-confirm">确定</button>
                </div>
            `;

            // --- 结束:创建所有“动态”模态框 ---


            const overlay = document.createElement('div');
            overlay.id = 'overlay';

            document.body.appendChild(assistant);
            document.body.appendChild(itemContextMenu);
            document.body.appendChild(categoryContextMenu);
            document.body.appendChild(editModal);
            document.body.appendChild(categoryModal);
            document.body.appendChild(overlay);
            // --- 新增:附加所有“动态”模态框 ---
            document.body.appendChild(detailModal);
            document.body.appendChild(deleteItemModal);
            document.body.appendChild(deleteCategoryModal);
            document.body.appendChild(renameCategoryModal);
            document.body.appendChild(alertModal); // 附加 alert modal
            // --- 结束:附加所有“动态”模态框 ---

            this.cache(); // 创建后立即缓存
        },

        cache: function() {
            // 缓存所有需要频繁访问的 DOM 元素
            this.elements.assistant = document.querySelector(Config.SELECTORS.ASSISTANT);
            this.elements.header = document.querySelector(Config.SELECTORS.HEADER);
            this.elements.title = document.querySelector(Config.SELECTORS.TITLE);
            this.elements.toggleBtn = document.querySelector(Config.SELECTORS.TOGGLE_BTN);
            this.elements.fixBtn = document.querySelector(Config.SELECTORS.FIX_BTN);
            this.elements.closeBtn = document.querySelector(Config.SELECTORS.CLOSE_BTN);
            this.elements.categoryContainer = document.querySelector(Config.SELECTORS.CATEGORY_CONTAINER);
            this.elements.itemsContainer = document.querySelector(Config.SELECTORS.ITEMS_CONTAINER);
            this.elements.searchInput = document.querySelector(Config.SELECTORS.SEARCH_INPUT);
            this.elements.addItemBtn = document.querySelector(Config.SELECTORS.ADD_ITEM_BTN);
            this.elements.itemContextMenu = document.querySelector(Config.SELECTORS.CONTEXT_MENU);
            this.elements.editItemMenu = document.querySelector(Config.SELECTORS.EDIT_ITEM_MENU);
            this.elements.deleteItemMenu = document.querySelector(Config.SELECTORS.DELETE_ITEM_MENU);
            this.elements.categoryContextMenu = document.querySelector(Config.SELECTORS.CATEGORY_CONTEXT_MENU);
            this.elements.renameCategoryMenu = document.querySelector(Config.SELECTORS.RENAME_CATEGORY_MENU);
            this.elements.deleteCategoryMenu = document.querySelector(Config.SELECTORS.DELETE_CATEGORY_MENU);
            this.elements.editModal = document.querySelector(Config.SELECTORS.EDIT_MODAL);
            this.elements.editTitle = document.querySelector(Config.SELECTORS.EDIT_TITLE);
            this.elements.editStartDate = document.querySelector(Config.SELECTORS.EDIT_START_DATE);
            this.elements.editEndDate = document.querySelector(Config.SELECTORS.EDIT_END_DATE);
            this.elements.editContent = document.querySelector(Config.SELECTORS.EDIT_CONTENT);
            this.elements.editCategory = document.querySelector(Config.SELECTORS.EDIT_CATEGORY);
            this.elements.cancelEditBtn = document.querySelector(Config.SELECTORS.CANCEL_EDIT_BTN);
            this.elements.saveEditBtn = document.querySelector(Config.SELECTORS.SAVE_EDIT_BTN);
            this.elements.categoryModal = document.querySelector(Config.SELECTORS.CATEGORY_MODAL);
            this.elements.categoryNameInput = document.querySelector(Config.SELECTORS.CATEGORY_NAME_INPUT);
            this.elements.cancelCategoryBtn = document.querySelector(Config.SELECTORS.CANCEL_CATEGORY_BTN);
            this.elements.saveCategoryBtn = document.querySelector(Config.SELECTORS.SAVE_CATEGORY_BTN);
            this.elements.overlay = document.querySelector(Config.SELECTORS.OVERLAY);
            
            // --- 新增:缓存所有“动态”模态框及其内容 ---
            this.elements.detailModal = document.querySelector(Config.SELECTORS.DETAIL_MODAL);
            this.elements.detailTitle = document.querySelector(Config.SELECTORS.DETAIL_TITLE);
            this.elements.detailCategory = document.querySelector(Config.SELECTORS.DETAIL_CATEGORY);
            this.elements.detailDate = document.querySelector(Config.SELECTORS.DETAIL_DATE);
            this.elements.detailContent = document.querySelector(Config.SELECTORS.DETAIL_CONTENT);

            this.elements.deleteItemModal = document.querySelector(Config.SELECTORS.DELETE_ITEM_MODAL);
            this.elements.deleteCategoryModal = document.querySelector(Config.SELECTORS.DELETE_CATEGORY_MODAL);
            this.elements.renameCategoryModal = document.querySelector(Config.SELECTORS.RENAME_CATEGORY_MODAL);
            this.elements.alertModal = document.querySelector(Config.SELECTORS.ALERT_MODAL); // 缓存 alert modal
            // --- 结束:缓存所有“动态”模态框及其内容 ---
        },

        renderCategories: function() {
            const container = this.elements.categoryContainer;
            if (!container) return;
            
            const activeCategory = State.data.activeCategory;
            container.innerHTML = ''; // 清空

            // 1. 添加 "全部"
            const allBtn = document.createElement('button');
            allBtn.className = `${Config.CLASSES.CATEGORY_BTN} ${activeCategory === Config.DEFAULT_CATEGORY ? Config.CLASSES.ACTIVE : ''}`;
            allBtn.textContent = Config.DEFAULT_CATEGORY;
            allBtn.dataset.category = Config.DEFAULT_CATEGORY;
            allBtn.draggable = false;
            container.appendChild(allBtn);

            // 2. 添加所有自定义分类
            State.data.categories.forEach(category => {
                const btn = document.createElement('button');
                btn.className = `${Config.CLASSES.CATEGORY_BTN} ${activeCategory === category ? Config.CLASSES.ACTIVE : ''}`;
                btn.textContent = category;
                btn.dataset.category = category;
                btn.draggable = true;
                container.appendChild(btn);
            });

            // 3. 添加 "添加" 按钮
            const addBtn = document.createElement('button');
            addBtn.id = Config.SELECTORS.ADD_CATEGORY_BTN.slice(1); //
            addBtn.textContent = '+';
            addBtn.draggable = false;
            container.appendChild(addBtn);
        },

        renderItems: function() {
            const container = this.elements.itemsContainer;
            if (!container) return;

            const filterCategory = State.data.activeCategory;
            const searchTerm = this.elements.searchInput ? this.elements.searchInput.value : '';
            container.innerHTML = ''; // 清空

            let filteredItems = State.data.items.filter(item => {
                const categoryMatch = filterCategory === Config.DEFAULT_CATEGORY || item.category === filterCategory;
                const searchMatch = !searchTerm ||
                    (item.title && item.title.toLowerCase().includes(searchTerm.toLowerCase())) ||
                    (item.content && item.content.toLowerCase().includes(searchTerm.toLowerCase()));
                return categoryMatch && searchMatch;
            });

            filteredItems.sort((a, b) => a.order - b.order);

            filteredItems.forEach(item => {
                const itemEl = document.createElement('div');
                itemEl.className = Config.CLASSES.INFO_ITEM; // 修复:直接使用 Config.CLASSES.INFO_ITEM
                itemEl.dataset.id = item.id;
                itemEl.draggable = true;
                itemEl.innerHTML = `
                    <div class="info-item-header">
                        <div class="item-category">${item.category}</div>
                    </div>
                    <div class="item-title">${item.title}</div>
                    ${(item.startDate || item.endDate) ? `
                        <div class="item-date">
                            ${item.startDate && item.endDate ? `${item.startDate} - ${item.endDate}` : (item.startDate || item.endDate)}
                        </div>
                    ` : ''}
                `;
                container.appendChild(itemEl);
            });
        },

        update: function() {
            this.renderCategories();
            this.renderItems();
        }
    };

    /**
     * ----------------------------------------------------------------
     * 模块: UI (View-State Controller)
     * 负责 UI 状态管理 (Modals, Menus, Sidebar 状态)
     * ----------------------------------------------------------------
     */
    const UI = {
        // --- Context Menus ---
        showItemContextMenu: function(event, itemId) {
                const menu = DOM.elements.itemContextMenu;
            menu.style.left = `${event.clientX}px`;
            menu.style.top = `${event.clientY}px`;
            menu.style.display = 'block';
            menu.dataset.itemId = itemId;
        },
        hideItemContextMenu: function() {
                if(DOM.elements.itemContextMenu) DOM.elements.itemContextMenu.style.display = 'none';
        },
        showCategoryContextMenu: function(event, categoryName) {
            const menu = DOM.elements.categoryContextMenu;
            menu.style.left = `${event.clientX}px`;
            menu.style.top = `${event.clientY}px`;
            menu.style.display = 'block';
            menu.dataset.categoryName = categoryName;
        },
        hideCategoryContextMenu: function() {
            if(DOM.elements.categoryContextMenu) DOM.elements.categoryContextMenu.style.display = 'none';
        },

        // --- Modals (Edit / Add Item) ---
        showEditModal: function(itemId = null) {
            const modal = DOM.elements.editModal;
            const categorySelect = DOM.elements.editCategory;

            categorySelect.innerHTML = '';
            State.data.categories.forEach(category => {
                const option = document.createElement('option');
                option.value = category;
                option.textContent = category;
                categorySelect.appendChild(option);
            });

            if (itemId) {
                // 编辑模式
                const item = State.data.items.find(i => i.id === itemId);
                if (item) {
                    DOM.elements.editTitle.value = item.title;
                    DOM.elements.editStartDate.value = item.startDate || '';
                    DOM.elements.editEndDate.value = item.endDate || '';
                    DOM.elements.editContent.value = item.content;
                    categorySelect.value = item.category;
                    modal.dataset.itemId = itemId;
                    modal.querySelector('.modal-title').textContent = '编辑信息';
                }
            } else {
                // 新增模式
                DOM.elements.editTitle.value = '';
                DOM.elements.editStartDate.value = '';
                DOM.elements.editEndDate.value = '';
                DOM.elements.editContent.value = '';
                modal.dataset.itemId = '';
                modal.querySelector('.modal-title').textContent = '添加信息';

                if (State.data.activeCategory !== Config.DEFAULT_CATEGORY) {
                    categorySelect.value = State.data.activeCategory;
                }
            }

            modal.style.display = 'block';
            DOM.elements.overlay.style.display = 'block';
            DOM.elements.editTitle.focus();
        },
        hideEditModal: function() {
            DOM.elements.editModal.style.display = 'none';
            DOM.elements.overlay.style.display = 'none';
        },
        getEditModalData: function() {
            const modal = DOM.elements.editModal;
            return {
                id: modal.dataset.itemId || null,
                title: DOM.elements.editTitle.value.trim(),
                startDate: DOM.elements.editStartDate.value,
                endDate: DOM.elements.editEndDate.value,
                content: DOM.elements.editContent.value.trim(),
                category: DOM.elements.editCategory.value,
            };
        },

        // --- Modals (Category) ---
        showCategoryModal: function() {
            DOM.elements.categoryNameInput.value = '';
            DOM.elements.categoryModal.style.display = 'block';
            DOM.elements.overlay.style.display = 'block';
            DOM.elements.categoryNameInput.focus();
        },
        hideCategoryModal: function() {
            DOM.elements.categoryModal.style.display = 'none';
            DOM.elements.overlay.style.display = 'none';
        },
        getCategoryModalData: function() {
            return DOM.elements.categoryNameInput.value.trim();
        },
        
        // --- Modals (Confirmations) ---
        showDeleteItemConfirm: function(itemId, onConfirm) {
            let modal = DOM.elements.deleteItemModal; // 使用缓存的 modal
            if (!modal) return; // 安全检查

            modal.style.display = 'block';
            DOM.elements.overlay.style.display = 'block';

            const confirmBtn = modal.querySelector(Config.SELECTORS.CONFIRM_DELETE_ITEM_BTN);
            const cancelBtn = modal.querySelector(Config.SELECTORS.CANCEL_DELETE_ITEM_BTN);

            const close = () => {
                modal.style.display = 'none';
                DOM.elements.overlay.style.display = 'none';
                confirmBtn.removeEventListener('click', confirmHandler);
                cancelBtn.removeEventListener('click', close);
                DOM.elements.overlay.removeEventListener('click', overlayHandler);
            };
            
            const confirmHandler = () => {
                onConfirm(itemId);
                close();
            };
            
            const overlayHandler = (e) => {
                if (e.target === DOM.elements.overlay) close();
            };

            confirmBtn.addEventListener('click', confirmHandler);
            cancelBtn.addEventListener('click', close);
            DOM.elements.overlay.addEventListener('click', overlayHandler);
        },
        
        showDeleteCategoryConfirm: function(categoryName, onConfirm) {
            let modal = DOM.elements.deleteCategoryModal; // 使用缓存的 modal
            if (!modal) return; // 安全检查

            modal.querySelector('.delete-category-message').innerHTML = `确定要删除分类“<strong>${categoryName}</strong>”吗?`;
            modal.style.display = 'block';
            DOM.elements.overlay.style.display = 'block';
            
            const confirmBtn = modal.querySelector(Config.SELECTORS.CONFIRM_DELETE_CATEGORY_BTN);
            const cancelBtn = modal.querySelector(Config.SELECTORS.CANCEL_DELETE_CATEGORY_BTN);
            
            const close = () => {
                modal.style.display = 'none';
                DOM.elements.overlay.style.display = 'none';
                confirmBtn.removeEventListener('click', confirmHandler);
                cancelBtn.removeEventListener('click', close);
                DOM.elements.overlay.removeEventListener('click', overlayHandler);
            };

            const confirmHandler = () => {
                const moveItems = modal.querySelector('input[name="delete-option"]:checked').value === 'move';
                onConfirm(categoryName, moveItems);
                close();
            };

            const overlayHandler = (e) => {
                if (e.target === DOM.elements.overlay) close();
            };

            confirmBtn.addEventListener('click', confirmHandler);
            cancelBtn.addEventListener('click', close);
            DOM.elements.overlay.addEventListener('click', overlayHandler);
        },

        showRenameCategoryPrompt: function(oldName, onConfirm) {
            let modal = DOM.elements.renameCategoryModal; // 使用缓存的 modal
            if (!modal) return; // 安全检查
            
            modal.querySelector('.rename-category-message').innerHTML = `请为分类“<strong>${oldName}</strong>”输入新名称:`;
            const input = modal.querySelector(Config.SELECTORS.NEW_CATEGORY_NAME_INPUT);
            input.value = oldName;

            modal.style.display = 'block';
            DOM.elements.overlay.style.display = 'block';
            input.focus();

            const confirmBtn = modal.querySelector(Config.SELECTORS.CONFIRM_RENAME_CATEGORY_BTN);
            const cancelBtn = modal.querySelector(Config.SELECTORS.CANCEL_RENAME_CATEGORY_BTN);

            const close = () => {
                modal.style.display = 'none';
                DOM.elements.overlay.style.display = 'none';
                confirmBtn.removeEventListener('click', confirmHandler);
                cancelBtn.removeEventListener('click', close);
                DOM.elements.overlay.removeEventListener('click', overlayHandler);
            };

            const confirmHandler = () => {
                const newName = input.value.trim();
                if (onConfirm(oldName, newName)) {
                    close();
                }
            };
            
            const overlayHandler = (e) => {
                if (e.target === DOM.elements.overlay) close();
            };

            confirmBtn.addEventListener('click', confirmHandler);
            cancelBtn.addEventListener('click', close);
            DOM.elements.overlay.addEventListener('click', overlayHandler);
        },

        // --- Modals (Detail View) ---
        showDetailModal: function(item) {
            let modal = DOM.elements.detailModal; // 使用缓存的 modal
            if (!modal) return; // 安全检查
            
            DOM.elements.detailTitle.textContent = item.title;
            DOM.elements.detailCategory.textContent = item.category;
            
            const dateElement = DOM.elements.detailDate;
            
            // --- 修复开始 ---
            // 恢复日期显示逻辑,并移除错误粘贴的 HTML
            if (item.startDate || item.endDate) {
                dateElement.textContent = item.startDate && item.endDate 
                    ? `${item.startDate} - ${item.endDate}` 
                    : (item.startDate || item.endDate);
                // 显示包含日期的整个 .info-field
                if(dateElement.parentElement) dateElement.parentElement.style.display = "block";
            } else {
                // 隐藏包含日期的整个 .info-field
                if(dateElement.parentElement) dateElement.parentElement.style.display = "none";
            }
            
            DOM.elements.detailContent.textContent = item.content;
            modal.style.display = "block";
            // --- 修复结束 ---
        },
        hideDetailModal: function() {
            if (DOM.elements.detailModal) { // 使用缓存的 modal
                DOM.elements.detailModal.style.display = "none";
            }
        },

        showAlertModal: function(message, title = '提示') {
            let modal = DOM.elements.alertModal;
            if (!modal) return;

            modal.querySelector('#alert-modal-title').textContent = title;
            modal.querySelector('.alert-message').innerHTML = message;
            modal.style.display = 'block';

            const confirmBtn = modal.querySelector('#alert-modal-confirm');
            const close = () => {
                modal.style.display = 'none';
                confirmBtn.removeEventListener('click', close);
            };
            confirmBtn.addEventListener('click', close);
        },

        // --- Sidebar State ---
        expandSidebar: function() {
            DOM.elements.assistant.classList.remove(Config.CLASSES.COLLAPSED);
            DOM.elements.assistant.classList.add(Config.CLASSES.OPEN);
            State.ui.isExpanded = true;
        },
        collapseSidebar: function() {
            // 在折叠侧边栏时关闭所有右键菜单
            UI.hideItemContextMenu();
            UI.hideCategoryContextMenu();
            
            DOM.elements.assistant.classList.remove(Config.CLASSES.OPEN);
            DOM.elements.assistant.classList.add(Config.CLASSES.COLLAPSED);
            State.ui.isExpanded = false;
        },
        toggleSidebarPosition: function() {
            const assistant = DOM.elements.assistant;
            const toggleBtn = DOM.elements.toggleBtn;

            if (assistant.classList.contains(Config.CLASSES.LEFT)) {
                assistant.classList.remove(Config.CLASSES.LEFT);
                toggleBtn.textContent = '◀';
                toggleBtn.title = '移到左侧';
                State.data.sidebarPosition = 'right';
            } else {
                assistant.classList.add(Config.CLASSES.LEFT);
                toggleBtn.textContent = '▶';
                toggleBtn.title = '移到右侧';
                State.data.sidebarPosition = 'left';
            }
            Storage.save();
        },
        applyFixedState: function(isFixed) {
            const assistant = DOM.elements.assistant;
            const fixBtn = DOM.elements.fixBtn;
            if (isFixed) {
                assistant.classList.add(Config.CLASSES.FIXED);
                fixBtn.textContent = '🔒';
                fixBtn.title = '固定';
            } else {
                assistant.classList.remove(Config.CLASSES.FIXED);
                fixBtn.textContent = '🔓';
                fixBtn.title = '取消固定';
            }
        },
        applySidebarPosition: function(position) {
            const assistant = DOM.elements.assistant;
            if (position === 'left') {
                assistant.classList.add(Config.CLASSES.LEFT);
            } else {
                assistant.classList.remove(Config.CLASSES.LEFT);
            }
        },

        // --- Sidebar Drag (Collapsed) ---
        startSidebarDrag: function(e) {
            State.ui.isDraggingSidebar = true;
            State.ui.hasDraggedSidebar = true;
            State.ui.sidebarDragStartY = e.clientY;
            
            const computedStyle = window.getComputedStyle(DOM.elements.assistant);
            State.ui.sidebarDragStartTop = parseFloat(computedStyle.top) || 0;
            
            DOM.elements.assistant.classList.remove(Config.CLASSES.DRAG_READY);
            DOM.elements.assistant.classList.add(Config.CLASSES.DRAGGING);
            
            document.body.style.userSelect = 'none';
            document.body.style.cursor = 'ns-resize';
        },
        handleSidebarDrag: function(e) {
            if (!State.ui.isDraggingSidebar) return;

            if (State.ui.lastSidebarAnimationFrame) {
                cancelAnimationFrame(State.ui.lastSidebarAnimationFrame);
            }

            State.ui.lastSidebarAnimationFrame = requestAnimationFrame(() => {
                const currentTime = Date.now();
                const deltaY = e.clientY - State.ui.sidebarDragStartY;
                const newTop = State.ui.sidebarDragStartTop + deltaY;
                
                if (State.ui.lastSidebarDragTime > 0) {
                    const deltaTime = currentTime - State.ui.lastSidebarDragTime;
                    if (deltaTime > 0) {
                        State.ui.sidebarDragVelocity = (deltaY - State.ui.lastSidebarDragY) / deltaTime;
                    }
                }
                State.ui.lastSidebarDragY = deltaY;
                State.ui.lastSidebarDragTime = currentTime;
                
                const viewportHeight = window.innerHeight;
                const sidebarHeight = DOM.elements.assistant.offsetHeight;
                const minTop = 0;
                const maxTop = viewportHeight - sidebarHeight;
                
                let clampedTop = Math.max(minTop, Math.min(maxTop, newTop));
                
                if (newTop < minTop) {
                    const overshoot = minTop - newTop;
                    clampedTop = minTop - Math.min(overshoot * 0.3, 20);
                } else if (newTop > maxTop) {
                    const overshoot = newTop - maxTop;
                    clampedTop = maxTop + Math.min(overshoot * 0.3, 20);
                }
                
                DOM.elements.assistant.style.setProperty('--collapsed-top', clampedTop + 'px');
            });
        },
        endSidebarDrag: function() {
            State.ui.isDraggingSidebar = false;
            
            if (State.ui.lastSidebarAnimationFrame) {
                cancelAnimationFrame(State.ui.lastSidebarAnimationFrame);
                State.ui.lastSidebarAnimationFrame = null;
            }
            
            setTimeout(() => {
                DOM.elements.assistant.classList.remove(Config.CLASSES.DRAGGING);
            }, 10);
            
            document.body.style.userSelect = '';
            document.body.style.cursor = '';
            
            if (DOM.elements.assistant.classList.contains(Config.CLASSES.COLLAPSED)) {
                const currentTop = parseFloat(DOM.elements.assistant.style.getPropertyValue('--collapsed-top')) || 0;
                State.data.collapsedPosition = { top: currentTop };
                Storage.save();
            }
            
            State.ui.lastSidebarDragY = 0;
            State.ui.sidebarDragVelocity = 0;
            State.ui.lastSidebarDragTime = 0;
        },
        restoreCollapsedPosition: function() {
            if (State.data.collapsedPosition && DOM.elements.assistant.classList.contains(Config.CLASSES.COLLAPSED)) {
                const { top } = State.data.collapsedPosition;
                const viewportHeight = window.innerHeight;
                const sidebarHeight = DOM.elements.assistant.offsetHeight;
                const minTop = 0;
                const maxTop = viewportHeight - sidebarHeight;
                const validTop = Math.max(minTop, Math.min(maxTop, parseInt(top) || viewportHeight / 2 - sidebarHeight / 2));
                
                DOM.elements.assistant.style.setProperty('--collapsed-top', validTop + 'px');
            }
        },

        // --- Helper ---
        isClickInExcludedElements: function(target) {
            const excludedIds = [
                Config.SELECTORS.EDIT_MODAL,
                Config.SELECTORS.CATEGORY_MODAL,
                Config.SELECTORS.DELETE_ITEM_MODAL,
                Config.SELECTORS.DELETE_CATEGORY_MODAL,
                Config.SELECTORS.RENAME_CATEGORY_MODAL,
                Config.SELECTORS.CONTEXT_MENU,
                Config.SELECTORS.CATEGORY_CONTEXT_MENU,
                Config.SELECTORS.OVERLAY,
                Config.SELECTORS.DETAIL_MODAL,
                Config.SELECTORS.ALERT_MODAL, // 新增:通用提示框
            ];

            for (const id of excludedIds) {
                if (target.closest(id)) {
                    return true;
                }
            }
            return false;
        }
    };

    /**
     * ----------------------------------------------------------------
     * 模块: TinyMCEIntegrator
     * 封装所有与 TinyMCE 编辑器集成的逻辑
     * ----------------------------------------------------------------
     */
    const TinyMCEIntegrator = {
        init: function() {
            this.checkAndBindExisting();
            this.observeForNewEditors();
            console.log("[TinyMCE] 事件监听初始化完成 (Observer + 短轮询).");

            // 自动填充的全局监听
            document.addEventListener('mouseup', (e) => {
                if (State.autofill.lastClickedItemContent) {
                    console.log('[AutoFill Debug] 执行自动填充');
                    
                    // 检查是否点击在 TinyMCE 实例上
                    const tinymceEditor = this.findEditorForElement(e.target);
                    if (tinymceEditor) {
                        console.log(`[AutoFill Debug] 填充到 TinyMCE: ${tinymceEditor.id}`);
                        tinymceEditor.setContent(State.autofill.lastClickedItemContent);
                    } else {
                        // 否则,使用通用模拟输入
                        Utils.simulateInputAtCursor(State.autofill.lastClickedItemContent);
                    }
                    
                    this.clearAutofill();
                }
            });
        },
        
        findEditorForElement: function(element) {
            if (!unsafeWindow.tinymce || !unsafeWindow.tinymce.editors) return null;
            
            for (const editor of unsafeWindow.tinymce.editors) {
                if (editor.iframeElement && editor.iframeElement.contains(element)) {
                    return editor;
                }
                if (editor.inline && editor.getBody().contains(element)) {
                    return editor;
                }
            }
            // 检查 e.target 是否是某个 editor 的 iframe
            const iframe = element.closest('iframe.tox-edit-area__iframe');
            if (iframe) {
                return unsafeWindow.tinymce.editors.find(ed => ed.iframeElement === iframe);
            }
            
            return null;
        },

        clearAutofill: function() {
            State.autofill.lastClickedItemContent = null;
            if (State.autofill.timeout) {
                clearTimeout(State.autofill.timeout);
                State.autofill.timeout = null;
            }
        },

        setAutofill: function(content) {
            this.clearAutofill();
            State.autofill.lastClickedItemContent = content;
            State.autofill.timeout = setTimeout(() => {
                this.clearAutofill();
                console.log('[AutoFill Debug] 自动填充超时,已清除缓存的内容');
            }, Config.TIMERS.AUTO_FILL_TIMEOUT);
        },

        bindEditorEvents: function(editor) {
            if (!editor || editor._tampermonkeyBound) return;
            editor._tampermonkeyBound = true;
            console.log("[TinyMCE] 绑定事件:", editor.id);

            editor.on('init', () => {
                const iframe = editor.iframeElement;
                if (iframe && iframe.contentDocument) {
                    const doc = iframe.contentDocument;
                    doc.addEventListener('click', (e) => {
                        if (State.autofill.lastClickedItemContent) {
                            editor.setContent(State.autofill.lastClickedItemContent);
                            this.clearAutofill();
                            console.log(`[TinyMCE DOM] [${editor.id}] Clicked and filled:`, e.target);
                        }
                    });
                    console.log(`[TinyMCE] DOM 监听已挂载 -> ${editor.id}`);
                }
            });
        },

        checkAndBindExisting: function() {
            let bound = false;
            if (unsafeWindow.tinymce && unsafeWindow.tinymce.editors) {
                unsafeWindow.tinymce.editors.forEach(editor => {
                    if (editor && !editor.destroyed && !editor._tampermonkeyBound) {
                        this.bindEditorEvents(editor);
                        bound = true;
                    }
                });
            }
            return bound;
        },

        startShortPolling: function() {
            let elapsed = 0;
            const interval = setInterval(() => {
                if (this.checkAndBindExisting()) {
                    clearInterval(interval);
                    return;
                }
                elapsed += Config.TIMERS.TINYMCE_POLL_INTERVAL;
                if (elapsed >= Config.TIMERS.TINYMCE_POLL_TIMEOUT) {
                    clearInterval(interval);
                }
            }, Config.TIMERS.TINYMCE_POLL_INTERVAL);
        },

        observeForNewEditors: function() {
            const observer = new MutationObserver((mutations) => {
                for (const mutation of mutations) {
                    if (mutation.addedNodes.length > 0) {
                        this.startShortPolling();
                        break;
                    }
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });
        }
    };

    /**
     * ----------------------------------------------------------------
     * 模块: Handlers
     * 存放所有事件处理函数 (业务逻辑)
     * ----------------------------------------------------------------
     */
    const Handlers = {
        // --- Sidebar Controls ---
        onTogglePositionClick: function(e) {
            e.stopPropagation();
            UI.toggleSidebarPosition();
        },
        onFixBtnClick: function(e) {
            e.stopPropagation();
            State.data.isFixed = !State.data.isFixed;
            UI.applyFixedState(State.data.isFixed);
            Storage.save();
        },
        onCloseBtnClick: function(e) {
            e.stopPropagation();
            UI.collapseSidebar();
        },
        onTitleClick: function(e) {
            e.stopPropagation();
            if (DOM.elements.assistant.classList.contains(Config.CLASSES.COLLAPSED) && !State.ui.hasDraggedSidebar) {
                UI.expandSidebar();
            } else {
                UI.collapseSidebar();
            }
        },
        onAssistantClick: function(e) {
            if (DOM.elements.assistant.classList.contains(Config.CLASSES.COLLAPSED) &&
                !e.target.closest(Config.SELECTORS.CONTROLS) &&
                e.target.id !== Config.SELECTORS.TITLE.slice(1) &&
                !State.ui.hasDraggedSidebar) {
                UI.expandSidebar();
            }
        },

        // --- Sidebar Drag ---
        onSidebarMouseDown: function(e) {
            if (DOM.elements.assistant.classList.contains(Config.CLASSES.COLLAPSED)) {
                e.preventDefault();
                DOM.elements.assistant.classList.add(Config.CLASSES.DRAG_READY);
                State.ui.hasDraggedSidebar = false;
                
                State.ui.sidebarLongPressTimer = setTimeout(() => {
                    UI.startSidebarDrag(e);
                }, Config.TIMERS.SIDEBAR_DRAG_START_DELAY);
            }
        },
        onDocumentMouseMove: function(e) {
            if (State.ui.isDraggingSidebar) {
                UI.handleSidebarDrag(e);
            }
        },
        onDocumentMouseUp: function() {
            DOM.elements.assistant.classList.remove(Config.CLASSES.DRAG_READY);
            if (State.ui.sidebarLongPressTimer) {
                clearTimeout(State.ui.sidebarLongPressTimer);
                State.ui.sidebarLongPressTimer = null;
            }
            if (State.ui.isDraggingSidebar) {
                UI.endSidebarDrag();
            }
        },

        // --- Global Clicks / Keys ---
        onDocumentClick: function(e) {
            // 1. 关闭右键菜单
            UI.hideItemContextMenu();
            UI.hideCategoryContextMenu();
            
            // 2. 点击外部自动收起侧边栏
            const assistant = DOM.elements.assistant;
            if (!assistant.contains(e.target) &&
                State.ui.isExpanded &&
                !State.data.isFixed &&
                !UI.isClickInExcludedElements(e.target)) {
                UI.collapseSidebar();
            }
        },
        onDocumentKeydown: function(e) {
            // ESC 关闭 Modals
            if (e.key === 'Escape') {
                UI.hideEditModal();
                UI.hideCategoryModal();
                // (确认框由其各自的取消按钮处理)
            }
            // 快捷键 Alt+Shift+L
            if (e.key && e.key.toLowerCase() === 'l' && e.altKey && e.shiftKey) {
                e.preventDefault();
                State.ui.isExpanded ? UI.collapseSidebar() : UI.expandSidebar();
            }
            // Ctrl 键按下 (用于详情)
            if (State.hoverDetail.isMouseOver && State.hoverDetail.currentItem && (e.key === "Control" || e.key === "Ctrl")) {
                Handlers.startDetailTimer(State.hoverDetail.currentItem);
            }
        },
        onDocumentKeyup: function(e) {
            // Ctrl 键松开
            if (e.key === "Control" || e.key === "Ctrl") {
                if (State.hoverDetail.timer) clearTimeout(State.hoverDetail.timer);
                State.hoverDetail.timer = null;
                UI.hideDetailModal();
            }
        },
        startDetailTimer: function(itemEl) {
            if (State.hoverDetail.timer) clearTimeout(State.hoverDetail.timer);
            State.hoverDetail.timer = setTimeout(() => {
                const fullItem = State.data.items.find(i => i.id === itemEl.dataset.id);
                if (fullItem) UI.showDetailModal(fullItem);
            }, Config.TIMERS.DETAIL_HOVER_DELAY);
        },

        // --- Footer ---
        onSearchInput: function() {
            DOM.renderItems();
        },
        onAddItemClick: function() {
            UI.showEditModal(null);
        },

        // --- Modals (Item) ---
        onSaveItemClick: function() {
            const data = UI.getEditModalData();
            if (!data.title || !data.content) {
                UI.showAlertModal('请填写标题和内容');
                return;
            }
            Storage.saveItem(data);
            DOM.update();
            UI.hideEditModal();
        },
        onCancelEditClick: function() {
            UI.hideEditModal();
        },

        // --- Modals (Category) ---
        onSaveCategoryClick: function() {
            const name = UI.getCategoryModalData();
            if (name) {
                if (Storage.saveCategory(name)) {
                    DOM.update();
                    UI.hideCategoryModal();
                } else {
                    UI.showAlertModal('分类已存在!');
                }
            }
        },
        onCancelCategoryClick: function() {
            UI.hideCategoryModal();
        },

        // --- Category Container (Delegated) ---
        onCategoryContainerClick: function(e) {
            const categoryBtn = e.target.closest(Config.SELECTORS.CATEGORY_BTN);
            if (categoryBtn) {
                State.data.activeCategory = categoryBtn.dataset.category;

                // 关键修复:
                // 修复方法巧妙地利用了 JavaScript 的事件循环机制:使用 setTimeout(() => {}, 0) 将渲染操作延迟到下一个事件循环执行。
                // 如果不延迟,renderCategories() 会立即销毁 e.target (被点击的按钮),
                // 导致事件冒泡到 document 时, onDocumentClick 中的 assistant.contains(e.target) 会返回 false, 从而错误地关闭侧边栏。
                setTimeout(() => {
                    DOM.renderCategories();
                    DOM.renderItems();
                }, 0);
            }
            if (e.target.closest(Config.SELECTORS.ADD_CATEGORY_BTN)) {
                UI.showCategoryModal();
            }
        },
        onCategoryContainerContextMenu: function(e) {
            const categoryBtn = e.target.closest(Config.SELECTORS.CATEGORY_BTN);
            if (categoryBtn) {
                // 在打开新菜单前,关闭所有已打开的菜单
                UI.hideItemContextMenu();
                UI.hideCategoryContextMenu();
                const categoryName = categoryBtn.dataset.category;
                if (categoryName !== Config.DEFAULT_CATEGORY) {
                    UI.showCategoryContextMenu(e, categoryName);
                }
            }
        },
        onCategoryDragStart: function(e) {
            const categoryBtn = e.target.closest(Config.SELECTORS.CATEGORY_BTN);
            if (categoryBtn && categoryBtn.dataset.category !== Config.DEFAULT_CATEGORY) {
                e.stopPropagation();
                State.drag.draggedCategory = categoryBtn;
                categoryBtn.classList.add(Config.CLASSES.DRAGGING);
                e.dataTransfer.effectAllowed = 'move';
            }
        },
        onCategoryDragEnd: function(e) {
            if (State.drag.draggedCategory) {
                State.drag.draggedCategory.classList.remove(Config.CLASSES.DRAGGING);
            }
            if (State.drag.overCategory) {
                State.drag.overCategory.style.borderTop = 'none';
            }
            State.drag.draggedCategory = null;
            State.drag.overCategory = null;
        },
        onCategoryDragOver: function(e) {
            e.preventDefault();
            e.dataTransfer.dropEffect = 'move';
        },
        onCategoryDragEnter: function(e) {
            e.preventDefault();
            const categoryBtn = e.target.closest(Config.SELECTORS.CATEGORY_BTN);
            if (categoryBtn && categoryBtn !== State.drag.draggedCategory && categoryBtn !== State.drag.overCategory && categoryBtn.dataset.category !== Config.DEFAULT_CATEGORY) {
                if (State.drag.overCategory) {
                    State.drag.overCategory.style.borderTop = 'none';
                }
                State.drag.overCategory = categoryBtn;
                State.drag.overCategory.style.borderTop = '2px solid #4CAF50';
            }
        },
        onCategoryDragLeave: function(e) {
            const categoryBtn = e.target.closest(Config.SELECTORS.CATEGORY_BTN);
            if (categoryBtn && State.drag.overCategory === categoryBtn) {
                if (!categoryBtn.contains(e.relatedTarget)) {
                    categoryBtn.style.borderTop = 'none';
                    State.drag.overCategory = null;
                }
            }
        },
        onCategoryDrop: function(e) {
            e.preventDefault();
            const categoryBtn = e.target.closest(Config.SELECTORS.CATEGORY_BTN);
            if (categoryBtn && categoryBtn !== State.drag.draggedCategory && categoryBtn.dataset.category !== Config.DEFAULT_CATEGORY && State.drag.draggedCategory) {
                const draggedName = State.drag.draggedCategory.dataset.category;
                const targetName = categoryBtn.dataset.category;
                Storage.updateCategoryOrder(draggedName, targetName);
                DOM.renderCategories(); // 只重绘分类
            }
            if (State.drag.overCategory) {
                State.drag.overCategory.style.borderTop = 'none';
            }
            State.drag.overCategory = null;
        },
        
        // --- Category Context Menu ---
        onRenameCategoryMenuClick: function() {
            const categoryName = DOM.elements.categoryContextMenu.dataset.categoryName;
            UI.hideCategoryContextMenu();
            if (categoryName) {
                UI.showRenameCategoryPrompt(categoryName, (oldName, newName) => {
                    const success = Storage.renameCategory(oldName, newName);
                    if (success) DOM.update();
                    return success;
                });
            }
        },
        onDeleteCategoryMenuClick: function() {
            const categoryName = DOM.elements.categoryContextMenu.dataset.categoryName;
            UI.hideCategoryContextMenu();
            if (categoryName) {
                UI.showDeleteCategoryConfirm(categoryName, (name, moveItems) => {
                    Storage.deleteCategory(name, moveItems);
                    // 如果删除的是当前激活的分类,则切换到“全部”
                    if (State.data.activeCategory === name) {
                        State.data.activeCategory = Config.DEFAULT_CATEGORY;
                    }
                    DOM.update();
                });
            }
        },

        // --- Items Container (Delegated) ---
        onItemsContainerClick: function(e) {
            const itemEl = e.target.closest(Config.SELECTORS.INFO_ITEM);
            if (!itemEl) return;

            // 检查右键菜单是否可见
            if (DOM.elements.itemContextMenu.style.display === 'block') return;

            const itemId = itemEl.dataset.id;
            const item = State.data.items.find(i => i.id === itemId);
            if (!item) return;

            const isShift = e.shiftKey;
            const isCtrl = e.ctrlKey;

            if (isShift && isCtrl) {
                // Shift+Ctrl: 复制标题
                console.log('[AutoFill Debug] Shift+Ctrl: 复制标题');
                Utils.copyToClipboard(item.title);
            } else if (isCtrl) {
                // Ctrl: 复制内容
                console.log('[AutoFill Debug] Ctrl: 复制内容');
                Utils.copyToClipboard(item.content);
            } else if (isShift) {
                // Shift: 自动填充标题
                console.log('[AutoFill Debug] Shift: 准备填充标题');
                TinyMCEIntegrator.setAutofill(item.title);
            } else {
                // Click: 自动填充内容
                console.log('[AutoFill Debug] Click: 准备填充内容');
                TinyMCEIntegrator.setAutofill(item.content);
            }
        },
        onItemsContainerContextMenu: function(e) {
            const itemEl = e.target.closest(Config.SELECTORS.INFO_ITEM);
            if (itemEl) {
                UI.hideItemContextMenu();
                UI.hideCategoryContextMenu();
                const itemId = itemEl.dataset.id;
                UI.showItemContextMenu(e, itemId);
            }
        },
        onItemsContainerDragStart: function(e) {
            const itemEl = e.target.closest(Config.SELECTORS.INFO_ITEM);
            if (itemEl) {
                e.stopPropagation();
                State.drag.draggedItem = itemEl;
                itemEl.classList.add(Config.CLASSES.DRAGGING);
                e.dataTransfer.effectAllowed = 'move';
                // (自定义拖拽图像的逻辑可以加在这里)
            }
        },
        onItemsContainerDragEnd: function() {
            if (State.drag.draggedItem) {
                State.drag.draggedItem.classList.remove(Config.CLASSES.DRAGGING);
            }
            if (State.drag.overItem) {
                State.drag.overItem.style.borderTop = 'none';
            }
            State.drag.draggedItem = null;
            State.drag.overItem = null;
        },
        onItemsContainerDragOver: function(e) {
            e.preventDefault();
            e.dataTransfer.dropEffect = 'move';
        },
        onItemsContainerDragEnter: function(e) {
            e.preventDefault();
            const itemEl = e.target.closest(Config.SELECTORS.INFO_ITEM);
            if (itemEl && itemEl !== State.drag.draggedItem && itemEl !== State.drag.overItem) {
                if (State.drag.overItem) {
                    State.drag.overItem.style.borderTop = 'none';
                }
                State.drag.overItem = itemEl;
                State.drag.overItem.style.borderTop = '2px solid #4CAF50';
            }
        },
        onItemsContainerDragLeave: function(e) {
            const itemEl = e.target.closest(Config.SELECTORS.INFO_ITEM);
             if (itemEl && State.drag.overItem === itemEl) {
                if (!itemEl.contains(e.relatedTarget)) {
                    itemEl.style.borderTop = 'none';
                    State.drag.overItem = null;
                }
            }
        },
        onItemsContainerDrop: function(e) {
            e.preventDefault();
            const itemEl = e.target.closest(Config.SELECTORS.INFO_ITEM);
            if (itemEl && itemEl !== State.drag.draggedItem && State.drag.draggedItem) {
                const draggedId = State.drag.draggedItem.dataset.id;
                const targetId = itemEl.dataset.id;
                Storage.updateItemOrder(draggedId, targetId);
                DOM.renderItems(); // 只重绘 Items
            }
            if (State.drag.overItem) {
                State.drag.overItem.style.borderTop = 'none';
            }
            State.drag.overItem = null;
        },
        onItemsContainerMouseOver: function(e) {
            const itemEl = e.target.closest(Config.SELECTORS.INFO_ITEM);
            if (itemEl) {
                State.hoverDetail.isMouseOver = true;
                State.hoverDetail.currentItem = itemEl;
                if (e.ctrlKey) {
                    // 直接处理Ctrl+悬停逻辑,清除旧的timer并创建新的timer
                    Handlers.startDetailTimer(itemEl);
                }
            }
        },
        onItemsContainerMouseOut: function(e) {
            const itemEl = e.target.closest(Config.SELECTORS.INFO_ITEM);
            if (itemEl) {
                State.hoverDetail.isMouseOver = false;
                State.hoverDetail.currentItem = null;
                if (State.hoverDetail.timer) clearTimeout(State.hoverDetail.timer);
                State.hoverDetail.timer = null;
                UI.hideDetailModal();
            }
        },

        // --- Item Context Menu ---
        onEditItemMenuClick: function() {
            const itemId = DOM.elements.itemContextMenu.dataset.itemId;
                UI.hideItemContextMenu();
            if (itemId) {
                UI.showEditModal(itemId);
            }
        },
        onDeleteItemMenuClick: function() {
            const itemId = DOM.elements.itemContextMenu.dataset.itemId;
                UI.hideItemContextMenu();
            if (itemId) {
                UI.showDeleteItemConfirm(itemId, (id) => {
                    Storage.deleteItemById(id);
                    DOM.update();
                });
            }
        },

        // --- 右键菜单屏蔽 ---
        onAssistantContextMenu: function(e) {
            // 总是阻止浏览器的默认右键行为
            e.preventDefault();
            e.stopPropagation();
            
            // 检查是否点击在条目或分类按钮上
            const itemEl = e.target.closest(Config.SELECTORS.INFO_ITEM);
            const categoryBtn = e.target.closest(Config.SELECTORS.CATEGORY_BTN);
            
            // 如果不是点击在条目或分类按钮上,则关闭所有右键菜单
            if (!itemEl && !categoryBtn) {
                // 在右键点击侧边栏空白区域时关闭所有右键菜单
                UI.hideItemContextMenu();
                UI.hideCategoryContextMenu();
            }
            // 如果点击在条目或分类按钮上,不关闭菜单,让相应的右键菜单处理函数处理
            return false;
        },
    };


    /**
     * ----------------------------------------------------------------
     * 模块: Events
     * 集中管理所有 DOM 事件监听器的绑定
     * ----------------------------------------------------------------
     */
    const Events = {
        init: function() {
            const els = DOM.elements; //
            
            // --- Sidebar Controls ---
            els.toggleBtn.addEventListener('click', Handlers.onTogglePositionClick);
            els.fixBtn.addEventListener('click', Handlers.onFixBtnClick);
            els.closeBtn.addEventListener('click', Handlers.onCloseBtnClick);
            els.title.addEventListener('click', Handlers.onTitleClick);
            els.assistant.addEventListener('click', Handlers.onAssistantClick);
            
            // --- 屏蔽侧边栏右键菜单 ---
            els.assistant.addEventListener('contextmenu', Handlers.onAssistantContextMenu);

            // --- Sidebar Drag ---
            els.assistant.addEventListener('mousedown', Handlers.onSidebarMouseDown);
            document.addEventListener('mousemove', Handlers.onDocumentMouseMove);
            document.addEventListener('mouseup', Handlers.onDocumentMouseUp);

            // --- Global ---
            document.addEventListener('click', Handlers.onDocumentClick);
            document.addEventListener('keydown', Handlers.onDocumentKeydown);
            document.addEventListener('keyup', Handlers.onDocumentKeyup);
            window.addEventListener('resize', UI.restoreCollapsedPosition);
            
            // --- Footer ---
            els.searchInput.addEventListener('input', Handlers.onSearchInput);
            els.addItemBtn.addEventListener('click', Handlers.onAddItemClick);
            
            // --- Modals ---
            els.saveEditBtn.addEventListener('click', Handlers.onSaveItemClick);
            els.cancelEditBtn.addEventListener('click', Handlers.onCancelEditClick);
            els.saveCategoryBtn.addEventListener('click', Handlers.onSaveCategoryClick);
            els.cancelCategoryBtn.addEventListener('click', Handlers.onCancelCategoryClick);
            
            // --- Category Container (Event Delegation) ---
            els.categoryContainer.addEventListener('click', Handlers.onCategoryContainerClick);
            els.categoryContainer.addEventListener('contextmenu', Handlers.onCategoryContainerContextMenu);
            els.categoryContainer.addEventListener('dragstart', Handlers.onCategoryDragStart);
            els.categoryContainer.addEventListener('dragend', Handlers.onCategoryDragEnd);
            els.categoryContainer.addEventListener('dragover', Handlers.onCategoryDragOver);
            els.categoryContainer.addEventListener('dragenter', Handlers.onCategoryDragEnter);
            els.categoryContainer.addEventListener('dragleave', Handlers.onCategoryDragLeave);
            els.categoryContainer.addEventListener('drop', Handlers.onCategoryDrop);
            
            // --- Category Context Menu ---
            els.renameCategoryMenu.addEventListener('click', Handlers.onRenameCategoryMenuClick);
            els.deleteCategoryMenu.addEventListener('click', Handlers.onDeleteCategoryMenuClick);
            
            // --- Items Container (Event Delegation) ---
            els.itemsContainer.addEventListener('click', Handlers.onItemsContainerClick);
            els.itemsContainer.addEventListener('contextmenu', Handlers.onItemsContainerContextMenu);
            els.itemsContainer.addEventListener('dragstart', Handlers.onItemsContainerDragStart);
            els.itemsContainer.addEventListener('dragend', Handlers.onItemsContainerDragEnd);
            els.itemsContainer.addEventListener('dragover', Handlers.onItemsContainerDragOver);
            els.itemsContainer.addEventListener('dragenter', Handlers.onItemsContainerDragEnter);
            els.itemsContainer.addEventListener('dragleave', Handlers.onItemsContainerDragLeave);
            els.itemsContainer.addEventListener('drop', Handlers.onItemsContainerDrop);
            els.itemsContainer.addEventListener('mouseover', Handlers.onItemsContainerMouseOver);
            els.itemsContainer.addEventListener('mouseout', Handlers.onItemsContainerMouseOut);
            
            // --- Item Context Menu ---
            els.editItemMenu.addEventListener('click', Handlers.onEditItemMenuClick);
            els.deleteItemMenu.addEventListener('click', Handlers.onDeleteItemMenuClick);
        }
    };

    /**
     * ----------------------------------------------------------------
     * 模块: App
     * 应用主控制器,负责初始化和协调所有模块
     * ----------------------------------------------------------------
     */
    const App = {
        init: function() {
            console.log("正在启动 Resumer (Refactored)...");
            
            // 1. 注入样式
            StyleManager.inject();

            // 2. 加载数据
            Storage.initialize();
            Storage.load();

            // 3. 创建并渲染 DOM
            DOM.create(); // 内部已包含 DOM.cache()
            DOM.update();

            // 4. 恢复 UI 状态
            UI.applyFixedState(State.data.isFixed);
            UI.applySidebarPosition(State.data.sidebarPosition);
            UI.restoreCollapsedPosition(); // 恢复收起时的位置

            // 5. 绑定所有事件
            Events.init();

            // 6. 初始化第三方集成
            TinyMCEIntegrator.init();

            console.log("Resumer (Refactored) 启动完成。");
        }
    };

    // --- 启动应用 ---
    App.init();

})();

QingJ © 2025

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