// ==UserScript==
// @name 跨网站同步待办事项管理器
// @namespace http://tampermonkey.net/
// @version 2.0
// @description 在任意网站中同步的待办事项管理工具,支持分类文件夹和导出功能
// @author 您的名称
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_addStyle
// @require https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js
// @resource fontAwesomeCSS https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css
// ==/UserScript==
(function() {
'use strict';
// 添加Font Awesome样式
GM_addStyle(`
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css');
`);
// 添加主样式
GM_addStyle(`
#sync-todo-app-container {
position: fixed;
top: 20px;
right: 20px;
width: 380px;
background: #fff;
border-radius: 12px;
box-shadow: 0 6px 25px rgba(0,0,0,0.15);
z-index: 10000;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-height: 80vh;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
}
#sync-todo-header {
padding: 18px;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
color: white;
border-radius: 12px 12px 0 0;
cursor: move;
display: flex;
justify-content: space-between;
align-items: center;
}
#sync-todo-content {
padding: 18px;
overflow-y: auto;
flex-grow: 1;
}
#sync-status {
padding: 8px 12px;
background: #f1f8ff;
border-radius: 6px;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #0366d6;
}
.sync-todo-section {
margin-bottom: 15px;
}
.sync-input-group {
display: flex;
gap: 8px;
margin-bottom: 15px;
}
.sync-input-group input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
outline: none;
}
.sync-input-group button {
padding: 10px 16px;
background: #6a11cb;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
}
#sync-todo-list {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 15px;
max-height: 300px;
overflow-y: auto;
}
.sync-todo-item {
padding: 12px;
background: #f8f9fa;
border-radius: 8px;
display: flex;
align-items: center;
gap: 12px;
}
.sync-todo-item input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.sync-todo-item span {
flex-grow: 1;
}
.sync-todo-item.completed span {
text-decoration: line-through;
color: #888;
}
.sync-todo-item button {
background: none;
border: none;
color: #dc3545;
cursor: pointer;
padding: 5px;
}
#sync-actions {
display: flex;
justify-content: space-between;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #eee;
}
#sync-actions button {
padding: 10px 16px;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
}
#sync-export-btn {
background: #00b894;
}
#sync-import-btn {
background: #fd7e14;
}
#sync-todo-minimized {
position: fixed;
bottom: 20px;
right: 20px;
width: 50px;
height: 50px;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
color: white;
border-radius: 50%;
display: none;
justify-content: center;
align-items: center;
cursor: pointer;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
z-index: 9999;
}
.sync-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 10001;
}
.sync-modal-content {
background: white;
padding: 25px;
border-radius: 12px;
width: 320px;
box-shadow: 0 5px 20px rgba(0,0,0,0.15);
}
.sync-modal-header {
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.sync-modal-title {
font-size: 1.2rem;
font-weight: 600;
}
.sync-modal-close {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: #999;
}
.sync-modal-body {
margin-bottom: 20px;
}
.sync-modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
.sync-modal-btn {
padding: 8px 16px;
border: none;
border-radius: 6px;
cursor: pointer;
}
.sync-modal-btn.primary {
background: #6a11cb;
color: white;
}
.sync-modal-btn.secondary {
background: #f8f9fa;
color: #333;
border: 1px solid #ddd;
}
#sync-category-select {
width: 100%;
padding: 10px;
border-radius: 6px;
border: 1px solid #ddd;
background: white;
margin-bottom: 15px;
}
`);
// 创建UI容器
const container = document.createElement('div');
container.id = 'sync-todo-app-container';
container.innerHTML = `
`;
// 创建最小化时的图标
const minimizedIcon = document.createElement('div');
minimizedIcon.id = 'sync-todo-minimized';
minimizedIcon.innerHTML = ``;
// 添加到页面
document.body.appendChild(container);
document.body.appendChild(minimizedIcon);
// 初始化数据
let categories = GM_getValue('todoCategories', ['工作', '个人', '学习']);
let todos = GM_getValue('todoItems', []);
let currentCategory = categories.length > 0 ? categories[0] : '';
// 初始化拖拽功能
let isDragging = false;
let dragOffsetX, dragOffsetY;
document.getElementById('sync-todo-header').addEventListener('mousedown', function(e) {
isDragging = true;
dragOffsetX = e.clientX - container.getBoundingClientRect().left;
dragOffsetY = e.clientY - container.getBoundingClientRect().top;
container.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', function(e) {
if (isDragging) {
container.style.left = (e.clientX - dragOffsetX) + 'px';
container.style.top = (e.clientY - dragOffsetY) + 'px';
container.style.right = 'unset';
}
});
document.addEventListener('mouseup', function() {
isDragging = false;
container.style.cursor = 'default';
});
// 最小化和关闭功能
document.getElementById('sync-minimize-btn').addEventListener('click', function() {
container.style.display = 'none';
minimizedIcon.style.display = 'flex';
});
document.getElementById('sync-close-btn').addEventListener('click', function() {
if (confirm('确定要关闭待办事项管理器吗?')) {
container.style.display = 'none';
minimizedIcon.style.display = 'flex';
}
});
minimizedIcon.addEventListener('click', function() {
container.style.display = 'flex';
minimizedIcon.style.display = 'none';
});
// 初始化UI
renderCategorySection();
renderTodos();
// 添加分类功能
document.getElementById('sync-add-category-btn').addEventListener('click', showAddCategoryModal);
// 添加待办事项功能
document.getElementById('sync-add-todo-btn').addEventListener('click', addNewTodo);
document.getElementById('sync-new-todo-input').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
addNewTodo();
}
});
// 分类选择事件
document.getElementById('sync-category-select').addEventListener('change', function() {
currentCategory = this.value;
renderTodos();
});
// 导出导入功能
document.getElementById('sync-export-btn').addEventListener('click', exportDataToFile);
document.getElementById('sync-import-btn').addEventListener('click', triggerImport);
// 立即同步按钮
document.getElementById('force-sync-btn').addEventListener('click', function() {
updateStatus('数据已同步', 'success');
});
// 渲染分类部分
function renderCategorySection() {
const select = document.getElementById('sync-category-select');
select.innerHTML = '';
if (categories.length === 0) {
const option = document.createElement('option');
option.textContent = '暂无分类,请先创建';
option.value = '';
select.appendChild(option);
return;
}
categories.forEach(category => {
const option = document.createElement('option');
option.value = category;
option.textContent = category;
if (category === currentCategory) {
option.selected = true;
}
select.appendChild(option);
});
}
// 渲染待办事项
function renderTodos() {
const listSection = document.getElementById('sync-todo-list');
const filteredTodos = currentCategory ? todos.filter(todo => todo.category === currentCategory) : [];
if (filteredTodos.length === 0) {
listSection.innerHTML = '
该分类下暂无待办事项
';
return;
}
let html = '';
filteredTodos.forEach(todo => {
html += `
${todo.text}
`;
});
listSection.innerHTML = html;
// 添加事件监听
document.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
checkbox.addEventListener('change', toggleTodoStatus);
});
document.querySelectorAll('.sync-delete-todo-btn').forEach(button => {
button.addEventListener('click', deleteTodo);
});
}
// 显示添加分类模态框
function showAddCategoryModal() {
const modal = document.createElement('div');
modal.className = 'sync-modal';
modal.innerHTML = `
`;
document.body.appendChild(modal);
document.getElementById('sync-cancel-category-btn').addEventListener('click', function() {
document.body.removeChild(modal);
});
document.querySelector('.sync-modal-close').addEventListener('click', function() {
document.body.removeChild(modal);
});
document.getElementById('sync-save-category-btn').addEventListener('click', function() {
const name = document.getElementById('sync-new-category-name').value.trim();
if (name && !categories.includes(name)) {
categories.push(name);
GM_setValue('todoCategories', categories);
currentCategory = name;
renderCategorySection();
renderTodos();
document.body.removeChild(modal);
updateStatus('分类已添加', 'success');
}
});
}
// 添加新待办事项
function addNewTodo() {
const input = document.getElementById('sync-new-todo-input');
const text = input.value.trim();
if (!text) {
alert('请输入待办事项内容');
return;
}
if (!currentCategory) {
alert('请先创建或选择一个分类');
return;
}
const todo = {
id: Date.now(),
text: text,
category: currentCategory,
completed: false,
createdAt: new Date().toISOString()
};
todos.push(todo);
GM_setValue('todoItems', todos);
input.value = '';
renderTodos();
updateStatus('待办事项已添加', 'success');
}
// 切换待办事项状态
function toggleTodoStatus(e) {
const id = parseInt(e.target.getAttribute('data-id'));
const todo = todos.find(t => t.id === id);
if (todo) {
todo.completed = e.target.checked;
GM_setValue('todoItems', todos);
renderTodos();
updateStatus('状态已更新', 'success');
}
}
// 删除待办事项
function deleteTodo(e) {
const id = parseInt(e.target.closest('.sync-delete-todo-btn').getAttribute('data-id'));
if (confirm('确定要删除这个待办事项吗?')) {
todos = todos.filter(todo => todo.id !== id);
GM_setValue('todoItems', todos);
renderTodos();
updateStatus('待办事项已删除', 'success');
}
}
// 导出数据到文件
function exportDataToFile() {
const data = {
categories: categories,
todos: todos,
exportedAt: new Date().toISOString()
};
const jsonString = JSON.stringify(data, null, 2);
const blob = new Blob([jsonString], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `todo-data-${new Date().toISOString().slice(0, 10)}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
updateStatus('数据已导出', 'success');
}
// 触发导入
function triggerImport() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = function(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = JSON.parse(e.target.result);
if (confirm('确定要导入数据吗?这将覆盖当前所有数据。')) {
categories = data.categories || [];
todos = data.todos || [];
GM_setValue('todoCategories', categories);
GM_setValue('todoItems', todos);
if (categories.length > 0) {
currentCategory = categories[0];
}
renderCategorySection();
renderTodos();
updateStatus('数据已导入', 'success');
}
} catch (error) {
alert('导入失败:无效的数据格式');
}
};
reader.readAsText(file);
};
input.click();
}
// 更新状态
function updateStatus(message, status) {
const statusEl = document.getElementById('sync-status');
const icon = statusEl.querySelector('i');
statusEl.querySelector('span').textContent = message;
// 根据状态更新颜色
if (status === 'success') {
icon.style.color = '#28a745';
} else if (status === 'error') {
icon.style.color = '#dc3545';
} else if (status === 'syncing') {
icon.style.color = '#ffc107';
}
}
})();