// ==UserScript== // @name 个人待办事项清单 // @namespace http://tampermonkey.net/ // @version 0.1 // @description 浏览器右下角的个人待办事项清单,支持多个清单管理 // @author Your Name // @match *://*/* // @grant GM_getValue // @grant GM_setValue // @license MIT // @grant GM_addStyle // @downloadURL https://update.greasyfork.icu/scripts/529493/%E4%B8%AA%E4%BA%BA%E5%BE%85%E5%8A%9E%E4%BA%8B%E9%A1%B9%E6%B8%85%E5%8D%95.user.js // @updateURL https://update.greasyfork.icu/scripts/529493/%E4%B8%AA%E4%BA%BA%E5%BE%85%E5%8A%9E%E4%BA%8B%E9%A1%B9%E6%B8%85%E5%8D%95.meta.js // ==/UserScript== (function() { 'use strict'; // 获取用户主题设置 let userThemePreference = GM_getValue('todo-theme-preference', 'auto'); // 检测是否为暗色模式 function isDarkMode() { // 如果用户选择了特定主题,则直接返回 if (userThemePreference === 'dark') return true; if (userThemePreference === 'light') return false; // 自动模式下,检测系统偏好 if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { return true; } // 检测网站暗色模式 - 通过检查背景颜色 const bodyBg = window.getComputedStyle(document.body).backgroundColor; if (bodyBg) { // 将背景颜色转换为RGB值并判断亮度 const rgb = bodyBg.match(/\d+/g); if (rgb && rgb.length >= 3) { // 计算亮度 (0.299*R + 0.587*G + 0.114*B) const brightness = (0.299 * parseInt(rgb[0]) + 0.587 * parseInt(rgb[1]) + 0.114 * parseInt(rgb[2])) / 255; return brightness < 0.5; // 亮度小于0.5认为是暗色模式 } } // 检查是否有常见的暗色模式类 return document.documentElement.classList.contains('dark') || document.body.classList.contains('dark-mode') || document.body.classList.contains('darkmode') || document.body.classList.contains('dark-theme'); } // 设置当前模式 let darkMode = isDarkMode(); // 样式定义 - 根据当前模式使用不同的颜色变量 let colors = getThemeColors(darkMode); function getThemeColors(isDark) { return isDark ? { // 暗色模式颜色 bg: '#2c2c2c', textPrimary: '#e0e0e0', textSecondary: '#a0a0a0', btnBg: '#3498db', btnBgHover: '#2980b9', panelBg: '#333333', headerBg: '#2c3e50', itemBg: '#3a3a3a', itemBgHover: '#444444', itemBorder: '#484848', inputBg: '#444444', inputBorder: '#555555', danger: '#e74c3c', listItemsBg: '#2a2a2a', borderColor: 'rgba(255,255,255,0.1)' } : { // 亮色模式颜色 bg: 'white', textPrimary: '#333333', textSecondary: '#666666', btnBg: '#3498db', btnBgHover: '#2980b9', panelBg: 'white', headerBg: '#3498db', itemBg: '#f5f5f5', itemBgHover: '#e8e8e8', itemBorder: '#f1f1f1', inputBg: 'white', inputBorder: '#ddd', danger: '#ff6b6b', listItemsBg: '#fafafa', borderColor: '#eee' }; } // 获取定义好的CSS样式 function getStyles(colors) { return ` #todo-button { position: fixed; top: 50%; right: 0; transform: translateY(-50%); width: 32px; height: 32px; background-color: ${colors.btnBg}; border-radius: 50% 0 0 50%; display: flex; justify-content: center; align-items: center; color: white; font-size: 16px; cursor: pointer; z-index: 9999; box-shadow: 0 2px 5px rgba(0,0,0,0.3); transition: all 0.3s; } #todo-button:hover { transform: translateY(-50%) translateX(-5px); background-color: ${colors.btnBgHover}; } #todo-panel { position: fixed; bottom: 80px; right: 20px; width: 250px; max-height: 450px; background-color: ${colors.panelBg}; border-radius: 8px; z-index: 9998; box-shadow: 0 2px 10px rgba(0,0,0,0.3); display: none; flex-direction: column; overflow: hidden; border: 1px solid ${colors.itemBorder}; transition: background-color 0.3s; } #todo-header { padding: 10px; background-color: ${colors.headerBg}; color: white; font-weight: bold; font-size: 14px; display: flex; justify-content: center; align-items: center; text-align: center; transition: background-color 0.3s; } #todo-content { padding: 12px; overflow-y: auto; max-height: 400px; color: ${colors.textPrimary}; transition: color 0.3s; } .todo-list-button { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 8px 10px; margin-bottom: 6px; background-color: ${colors.itemBg}; border: none; border-radius: 4px; text-align: left; cursor: pointer; transition: all 0.2s; font-size: 14px; color: ${colors.textPrimary}; } .todo-list-button:hover { background-color: ${colors.itemBgHover}; } .todo-list-items { margin-top: 8px; margin-bottom: 15px; display: none; background-color: ${colors.listItemsBg}; border-radius: 4px; padding: 5px; transition: background-color 0.3s; } .todo-item { display: flex; align-items: center; padding: 6px 8px; border-bottom: 1px solid ${colors.itemBorder}; user-select: none; transition: border-color 0.3s; } .todo-item input[type="checkbox"] { margin-right: 8px; min-width: 16px; min-height: 16px; } .todo-item span { flex-grow: 1; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; color: ${colors.textPrimary}; transition: color 0.3s; } .todo-item button { background: none; border: none; color: ${colors.danger}; cursor: pointer; margin-left: 5px; font-size: 16px; opacity: 0.5; transition: opacity 0.2s, color 0.3s; } .todo-item:hover button { opacity: 1; } .todo-items-container { margin-bottom: 10px; max-height: 250px; overflow-y: auto; } .add-item-form { display: flex; margin-top: 8px; margin-bottom: 10px; } .add-item-form input { flex-grow: 1; padding: 6px 8px; border: 1px solid ${colors.inputBorder}; background-color: ${colors.inputBg}; color: ${colors.textPrimary}; border-radius: 4px 0 0 4px; font-size: 13px; height: 16px; line-height: 16px; box-sizing: content-box; transition: border-color 0.3s, background-color 0.3s, color 0.3s; } .add-item-form button { padding: 6px 10px; background-color: ${colors.btnBg}; color: white; border: none; border-radius: 0 4px 4px 0; cursor: pointer; transition: background-color 0.3s; } .completed { text-decoration: line-through; color: ${colors.textSecondary}; transition: color 0.3s; } #add-list-form { display: flex; margin-top: 20px; } #add-list-form input { flex-grow: 1; padding: 6px 8px; border: 1px solid ${colors.inputBorder}; background-color: ${colors.inputBg}; color: ${colors.textPrimary}; border-radius: 4px 0 0 4px; font-size: 13px; height: 16px; line-height: 16px; box-sizing: content-box; transition: border-color 0.3s, background-color 0.3s, color 0.3s; } #add-list-form button { padding: 6px 10px; background-color: ${colors.btnBg}; color: white; border: none; border-radius: 0 4px 4px 0; cursor: pointer; transition: background-color 0.3s; } .context-menu { position: fixed; background-color: ${colors.panelBg}; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.15); padding: 8px 0; z-index: 10000; display: none; min-width: 200px; max-width: 280px; border: 1px solid ${colors.borderColor}; opacity: 0; transform: translateY(10px); transition: opacity 0.2s, transform 0.2s, background-color 0.3s, border-color 0.3s; } .context-menu.visible { opacity: 1; transform: translateY(0); } .context-menu button { display: flex; align-items: center; width: 100%; padding: 10px 15px; text-align: left; background: none; border: none; cursor: pointer; font-size: 14px; color: ${colors.textPrimary}; transition: background-color 0.2s, color 0.3s; position: relative; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .context-menu button:hover { background-color: ${colors.itemBgHover}; } .context-menu button:active { background-color: ${darkMode ? '#555555' : '#eaeaea'}; } .context-menu .list-count { margin-left: auto; background-color: ${colors.btnBg}; color: white; border-radius: 12px; padding: 3px 8px; font-size: 12px; min-width: 20px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1); transition: background-color 0.3s; } .context-menu-title { padding: 5px 15px 8px; margin-top: -5px; color: ${colors.textSecondary}; font-size: 12px; border-bottom: 1px solid ${colors.borderColor}; margin-bottom: 5px; transition: color 0.3s, border-color 0.3s; } .context-menu .title-text { overflow: hidden; text-overflow: ellipsis; max-width: 180px; white-space: nowrap; display: inline-block; } .menu-icon { margin-right: 10px; font-size: 16px; color: ${colors.textSecondary}; display: inline-flex; align-items: center; justify-content: center; width: 20px; transition: color 0.3s; } .context-menu button:hover .menu-icon { color: ${colors.btnBg}; } .delete-list-button { margin-top: 10px; padding: 6px; background-color: ${colors.danger}; color: white; border: none; border-radius: 4px; cursor: pointer; width: 100%; transition: all 0.2s; font-size: 13px; } .delete-list-button:hover { background-color: ${darkMode ? '#c0392b' : '#ff4f4f'}; } .list-count { margin-left: 8px; background-color: ${colors.btnBg}; color: white; border-radius: 10px; padding: 2px 6px; font-size: 11px; min-width: 18px; text-align: center; transition: background-color 0.3s; } .action-buttons { display: flex; gap: 8px; margin-top: 15px; } .action-button { flex: 1; padding: 6px; background-color: ${colors.btnBg}; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; transition: all 0.2s; } .action-button:hover { background-color: ${colors.btnBgHover}; } #theme-toggle { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; padding: 10px; background-color: ${colors.itemBg}; border-radius: 4px; transition: background-color 0.3s; } #theme-toggle span { font-size: 13px; color: ${colors.textPrimary}; transition: color 0.3s; } #theme-toggle select { padding: 4px 8px; border-radius: 4px; border: 1px solid ${colors.inputBorder}; background-color: ${colors.inputBg}; color: ${colors.textPrimary}; font-size: 13px; transition: border-color 0.3s, background-color 0.3s, color 0.3s; } `; } // 应用当前主题样式 let styleElement = GM_addStyle(getStyles(colors)); // 数据存储和获取 const todoData = GM_getValue('todo-lists', {}); // 当前打开的清单名称 let currentOpenedListName = null; // 全局元素引用 let globalElements; // 创建DOM元素 function createElements() { // 创建主按钮 const todoButton = document.createElement('div'); todoButton.id = 'todo-button'; todoButton.innerHTML = '✓'; todoButton.title = '待办事项'; document.body.appendChild(todoButton); // 创建面板 const todoPanel = document.createElement('div'); todoPanel.id = 'todo-panel'; // 头部 const todoHeader = document.createElement('div'); todoHeader.id = 'todo-header'; todoHeader.innerHTML = '待办事项清单'; // 内容区域 const todoContent = document.createElement('div'); todoContent.id = 'todo-content'; // 添加到面板 todoPanel.appendChild(todoHeader); todoPanel.appendChild(todoContent); document.body.appendChild(todoPanel); // 创建右键菜单 const contextMenu = document.createElement('div'); contextMenu.className = 'context-menu'; document.body.appendChild(contextMenu); return { button: todoButton, panel: todoPanel, content: todoContent, contextMenu: contextMenu }; } // 初始化事件 function initEvents(elements) { // 点击按钮显示面板 elements.button.addEventListener('click', function(e) { elements.panel.style.display = elements.panel.style.display === 'flex' ? 'none' : 'flex'; e.stopPropagation(); // 如果显示面板,则更新内容 if (elements.panel.style.display === 'flex') { updatePanelContent(elements); } }); // 点击页面其他地方关闭面板和菜单 document.addEventListener('click', function(e) { // 检查点击目标是否在面板内或是否是待办按钮 // 如果是,则不关闭面板 if (e.target.closest('#todo-panel') || e.target.closest('#todo-button')) { return; } // 关闭面板和菜单 elements.panel.style.display = 'none'; // 添加菜单渐隐效果 if (elements.contextMenu.style.display === 'block') { elements.contextMenu.classList.remove('visible'); setTimeout(() => { elements.contextMenu.style.display = 'none'; }, 200); } else { elements.contextMenu.style.display = 'none'; } }); // 阻止面板点击事件冒泡 elements.panel.addEventListener('click', function(e) { e.stopPropagation(); }); // 右键点击主按钮 elements.button.addEventListener('contextmenu', function(e) { e.preventDefault(); // 检查是否有清单 const lists = Object.keys(todoData); if (lists.length === 0) { return; // 没有清单时不显示 } // 获取按钮位置以便显示菜单 const buttonRect = elements.button.getBoundingClientRect(); // 显示右键菜单在按钮的左侧 elements.contextMenu.style.display = 'block'; elements.contextMenu.style.left = (buttonRect.left - elements.contextMenu.offsetWidth - 10) + 'px'; elements.contextMenu.style.top = (buttonRect.top + buttonRect.height/2 - elements.contextMenu.offsetHeight/2) + 'px'; // 清除旧菜单项 elements.contextMenu.innerHTML = ''; // 添加菜单标题 const menuTitle = document.createElement('div'); menuTitle.className = 'context-menu-title'; menuTitle.innerHTML = '我的待办清单'; elements.contextMenu.appendChild(menuTitle); // 添加清单到菜单 lists.forEach(listName => { const menuItem = document.createElement('button'); // 添加图标和文本容器 const iconSpan = document.createElement('span'); iconSpan.className = 'menu-icon'; iconSpan.innerHTML = '📝'; menuItem.appendChild(iconSpan); // 添加文本和省略号支持 const textSpan = document.createElement('span'); textSpan.className = 'title-text'; textSpan.textContent = listName; menuItem.appendChild(textSpan); // 添加未完成项目计数 const list = todoData[listName] || []; const uncompletedCount = list.filter(item => !item.completed).length; if (uncompletedCount > 0) { const countSpan = document.createElement('span'); countSpan.className = 'list-count'; countSpan.textContent = uncompletedCount; menuItem.appendChild(countSpan); } // 点击清单按钮事件 menuItem.addEventListener('click', function(evt) { evt.stopPropagation(); // 阻止事件冒泡 openList(listName); elements.panel.style.display = 'flex'; elements.contextMenu.style.display = 'none'; elements.contextMenu.classList.remove('visible'); }); elements.contextMenu.appendChild(menuItem); }); // 添加动画效果 setTimeout(() => { elements.contextMenu.classList.add('visible'); }, 10); e.stopPropagation(); }); } // 更新面板内容 function updatePanelContent(elements) { const content = elements.content; content.innerHTML = ''; // 获取所有清单 const lists = Object.keys(todoData); if (lists.length === 0) { // 如果没有清单,显示欢迎信息 content.innerHTML = `
欢迎使用待办事项清单!
请创建您的第一个清单。