// ==UserScript== // @name 发送到 Memos // @name:en Send to Memos // @namespace https://github.com/hu3rror/my-userscript // @version 2.6.0 // @description 将选中的链接或文本发送到 Memos,支持配置、添加源页面信息和快速发送按钮 // @description:en Send selected links or text to Memos, supports configuration, adding source page information, and quick send buttons // @author Hu3rror // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @license MIT // @homepageURL https://github.com/hu3rror/my-userscript // @supportURL https://github.com/hu3rror/my-userscript/issues // @downloadURL https://update.greasyfork.icu/scripts/533386/%E5%8F%91%E9%80%81%E5%88%B0%20Memos.user.js // @updateURL https://update.greasyfork.icu/scripts/533386/%E5%8F%91%E9%80%81%E5%88%B0%20Memos.meta.js // ==/UserScript== (function() { 'use strict'; // 获取保存的配置或使用默认值 const MEMOS_API_URL = GM_getValue('MEMOS_API_URL', ''); const API_TOKEN = GM_getValue('API_TOKEN', ''); let isConfigured = MEMOS_API_URL && API_TOKEN; // 检测系统颜色主题 function isDarkMode() { return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; } // 添加样式(包含优化后的暗黑模式) GM_addStyle(` #memos-float-button { position: fixed; width: 36px; height: 36px; background-color: rgba(76, 175, 80, 0.7); color: white; border-radius: 50%; text-align: center; line-height: 36px; font-size: 16px; font-weight: bold; cursor: pointer; box-shadow: 1px 1px 3px rgba(0,0,0,0.2); z-index: 9999; user-select: none; display: none; /* 初始状态隐藏 */ align-items: center; justify-content: center; right: 20px; bottom: 20px; transition: all 0.3s ease; } #memos-float-button:hover { background-color: rgba(69, 160, 73, 0.9); opacity: 1; } .memos-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 10000; } .memos-modal-content { padding: 20px; border-radius: 8px; width: 400px; max-width: 90%; transition: all 0.3s ease; } .memos-modal-content.light-mode { background-color: white; color: #333; } .memos-modal-content.dark-mode { background-color: #1e1e1e; color: #e0e0e0; } .memos-modal h2 { margin-top: 0; } .memos-form-group { margin-bottom: 15px; } .memos-form-group label { display: block; margin-bottom: 5px; font-weight: bold; } .memos-form-group input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; transition: all 0.3s ease; } .memos-modal-content.light-mode .memos-form-group input { background-color: white; border-color: #ddd; color: #333; } .memos-modal-content.dark-mode .memos-form-group input { background-color: #2d2d2d; border-color: #3d3d3d; color: #e0e0e0; } .memos-button { padding: 8px 16px; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 10px; transition: all 0.3s ease; } .memos-button.save { background-color: #4caf50; } .memos-button.cancel { background-color: #f44336; } .memos-button:hover { opacity: 0.9; } .memos-buttons { display: flex; justify-content: flex-end; margin-top: 20px; } /* 优化后的暗黑模式按钮样式 */ .dark-mode #memos-float-button { background-color: rgba(60, 70, 60, 0.5); /* 更暗、更柔和的绿色 */ box-shadow: 0 1px 2px rgba(0,0,0,0.3); } .dark-mode #memos-float-button:hover { background-color: rgba(70, 80, 70, 0.7); /* 悬停时略微变亮 */ } /* 暗黑模式下的模态框按钮 */ .memos-modal-content.dark-mode .memos-button.save { background-color: #3a7d3f; /* 更暗的绿色 */ } .memos-modal-content.dark-mode .memos-button.cancel { background-color: #c53929; /* 更暗的红色 */ } `); // 创建自动关闭的通知 function showNotification(message, isError = false) { const notification = document.createElement('div'); notification.style.position = 'fixed'; notification.style.top = '20px'; notification.style.right = '20px'; notification.style.padding = '10px 20px'; notification.style.backgroundColor = isError ? '#f44336' : '#4caf50'; notification.style.color = 'white'; notification.style.borderRadius = '5px'; notification.style.zIndex = '9999'; notification.style.opacity = '0.9'; notification.style.transition = 'opacity 0.2s ease'; notification.textContent = message; document.body.appendChild(notification); // 1.5秒后淡出并移除 setTimeout(() => { notification.style.opacity = '0'; setTimeout(() => { notification.remove(); }, 500); // 等待淡出动画完成 }, 1500); } // 获取选中文本 function getSelectedText() { return window.getSelection().toString().trim(); } // 获取当前页面信息,以 Markdown 格式 function getPageInfo() { const pageTitle = document.title || '无标题页面'; const pageUrl = window.location.href; return `\n\n---\n**来源**:[${pageTitle}](${pageUrl})`; } // 发送内容到 Memos 的函数 function sendToMemos(content) { if (!isConfigured) { showNotification('请先配置 Memos API URL 和 Token', true); showConfigModal(); return; } // 添加页面来源信息 content += getPageInfo(); GM_xmlhttpRequest({ method: 'POST', url: MEMOS_API_URL, headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, data: JSON.stringify({ content: content, visibility: 'PRIVATE' // 可选:'PUBLIC', 'PROTECTED', 'PRIVATE' }), onload: function(response) { if (response.status === 200 || response.status === 201) { showNotification('成功发送到 Memos!'); } else { showNotification(`发送到 Memos 失败。状态码: ${response.status}\n可能端点错误,建议尝试 /api/v1/memo 或 /api/memos。`, true); } }, onerror: function(error) { showNotification('发送到 Memos 时出错: ' + error, true); } }); } // 存储当前右键点击的链接 let currentLink = null; // 监听右键点击事件,捕获链接 document.addEventListener('contextmenu', function(event) { currentLink = event.target.closest('a'); }, false); // 注册菜单命令 GM_registerMenuCommand('发送选中内容到 Memos', function() { handleSendAction(); }); GM_registerMenuCommand('配置 Memos API', function() { showConfigModal(); }); // 处理发送动作 function handleSendAction() { const selectedText = getSelectedText(); if (selectedText) { // 如果有选中文本,发送选中文本 sendToMemos(selectedText); } else if (currentLink && currentLink.href) { // 如果没有选中文本但有链接,发送链接 const linkText = currentLink.textContent.trim() || '无文本'; const linkUrl = currentLink.href; const content = `${linkText}\n${linkUrl}`; sendToMemos(content); } else { showNotification('请先选择文本或右键点击链接', true); } } // 清理 currentLink,防止重复使用 document.addEventListener('click', function() { currentLink = null; }, false); // 创建配置模态框 function showConfigModal() { // 创建模态框容器 const modal = document.createElement('div'); modal.className = 'memos-modal'; // 创建模态框内容 const modalContent = document.createElement('div'); modalContent.className = `memos-modal-content ${isDarkMode() ? 'dark-mode' : 'light-mode'}`; modalContent.innerHTML = `