// ==UserScript== // @name 网页剪藏工具 // @namespace http://tampermonkey.net/ // @version 1.3 // @description 一个可配置的网页剪藏工具,可将页面标题、URL和HTML源代码发送到指定后端。 // @author You // @match *://*/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_notification // @grant GM_xmlhttpRequest // 关键修改:添加此 grant // @connect * // 保留此指令,这是一个好习惯 // ==/UserScript== (function() { 'use strict'; // --- 配置和常量 --- const STORAGE_KEYS = { BACKEND_URL: 'webClipperBackendUrl', ACTIVE_SITES: 'webClipperActiveSites', BUTTON_POSITION: 'webClipperButtonPosition' }; const DEFAULT_SETTINGS = { backendUrl: 'https://your-backend.com/api/clipping', activeSites: [], buttonPosition: { top: '100px', right: '20px' } }; // --- 样式定义 --- GM_addStyle(` /* 样式已简化,只保留必要的定位和交互属性 */ #web-clipper-btn { position: fixed; z-index: 99999; cursor: move; user-select: none; /* 防止拖动时选中文字 */ font-size: 28px; /* 控制Emoji大小 */ } /* 设置面板样式保持不变 */ #web-clipper-settings-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 100000; display: none; align-items: center; justify-content: center; } #web-clipper-settings-panel { background: white; padding: 20px 30px; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.4); width: 400px; display: flex; flex-direction: column; gap: 15px; } #web-clipper-settings-panel h3 { margin-top: 0; text-align: center; } #web-clipper-settings-panel label { display: block; margin-bottom: 5px; font-weight: bold; } #web-clipper-settings-panel input, #web-clipper-settings-panel textarea { width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; } #web-clipper-settings-panel .buttons { display: flex; justify-content: flex-end; gap: 10px; margin-top: 10px; } #web-clipper-settings-panel button { padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; } #web-clipper-settings-panel .save-btn { background-color: #28a745; color: white; } #web-clipper-settings-panel .cancel-btn { background-color: #dc3545; color: white; } `); // --- 核心逻辑 --- let settings = { ...DEFAULT_SETTINGS }; let clipperButton = null; let settingsPanel = null; function loadSettings() { settings.backendUrl = GM_getValue(STORAGE_KEYS.BACKEND_URL, DEFAULT_SETTINGS.backendUrl); settings.activeSites = GM_getValue(STORAGE_KEYS.ACTIVE_SITES, DEFAULT_SETTINGS.activeSites); settings.buttonPosition = GM_getValue(STORAGE_KEYS.BUTTON_POSITION, DEFAULT_SETTINGS.buttonPosition); } function saveSettings() { GM_setValue(STORAGE_KEYS.BACKEND_URL, settings.backendUrl); GM_setValue(STORAGE_KEYS.ACTIVE_SITES, settings.activeSites); GM_setValue(STORAGE_KEYS.BUTTON_POSITION, settings.buttonPosition); } function checkIfSiteIsActive() { if (settings.activeSites.length === 0) { return true; } const currentHostname = window.location.hostname; return settings.activeSites.some(site => { const regex = new RegExp('^' + site.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$'); return regex.test(currentHostname); }); } function createFloatingButton() { clipperButton = document.createElement('div'); clipperButton.id = 'web-clipper-btn'; clipperButton.title = '点击剪藏,双击打开设置'; clipperButton.textContent = '⚡️'; // 应用保存的位置 Object.assign(clipperButton.style, settings.buttonPosition); document.body.appendChild(clipperButton); makeDraggable(clipperButton); // 单击:剪藏 clipperButton.addEventListener('click', (e) => { // 延迟执行以区别于拖动 setTimeout(() => clipPage(), 200); }); // 双击:打开设置 clipperButton.addEventListener('dblclick', (e) => { e.stopPropagation(); toggleSettingsPanel(true); }); } // --- 重写的拖拽函数,支持移动端 --- function makeDraggable(element) { let isDragging = false; let startX, startY, initialTop, initialRight; function handleStart(e) { isDragging = true; // 兼容鼠标和触摸事件 startX = e.type.includes('mouse') ? e.clientX : e.touches[0].clientX; startY = e.type.includes('mouse') ? e.clientY : e.touches[0].clientY; const rect = element.getBoundingClientRect(); initialTop = rect.top; initialRight = window.innerWidth - rect.right; element.style.cursor = 'grabbing'; } function handleMove(e) { if (!isDragging) return; // 阻止触摸设备的默认滚动行为 e.preventDefault(); const currentX = e.type.includes('mouse') ? e.clientX : e.touches[0].clientX; const currentY = e.type.includes('mouse') ? e.clientY : e.touches[0].clientY; const deltaY = currentY - startY; const deltaX = currentX - startX; const newTop = initialTop + deltaY; const newRight = initialRight - deltaX; element.style.top = `${newTop}px`; element.style.right = `${newRight}px`; element.style.left = 'auto'; element.style.bottom = 'auto'; } function handleEnd() { if (!isDragging) return; isDragging = false; element.style.cursor = 'move'; // 保存新位置 const style = window.getComputedStyle(element); settings.buttonPosition = { top: style.top, right: style.right }; saveSettings(); } // 鼠标事件 element.addEventListener('mousedown', handleStart); document.addEventListener('mousemove', handleMove); document.addEventListener('mouseup', handleEnd); // 触摸事件 // passive: false 是为了确保可以在 touchstart 和 touchmove 中调用 preventDefault element.addEventListener('touchstart', handleStart, { passive: false }); document.addEventListener('touchmove', handleMove, { passive: false }); document.addEventListener('touchend', handleEnd); } function createSettingsPanel() { const overlay = document.createElement('div'); overlay.id = 'web-clipper-settings-overlay'; const panel = document.createElement('div'); panel.id = 'web-clipper-settings-panel'; panel.innerHTML = `

剪藏工具设置

`; overlay.appendChild(panel); document.body.appendChild(overlay); settingsPanel = overlay; document.getElementById('backend-url').value = settings.backendUrl; document.getElementById('active-sites').value = settings.activeSites.join('\n'); overlay.addEventListener('click', (e) => { if (e.target === overlay) { toggleSettingsPanel(false); } }); panel.querySelector('.cancel-btn').addEventListener('click', () => toggleSettingsPanel(false)); panel.querySelector('.save-btn').addEventListener('click', () => { settings.backendUrl = document.getElementById('backend-url').value.trim(); const sitesText = document.getElementById('active-sites').value.trim(); settings.activeSites = sitesText ? sitesText.split('\n').map(s => s.trim()).filter(s => s) : []; saveSettings(); toggleSettingsPanel(false); showNotification('设置已保存!', false); }); } function toggleSettingsPanel(show) { if (show) { settingsPanel.style.display = 'flex'; } else { settingsPanel.style.display = 'none'; } } function showNotification(message, isError = false) { GM_notification({ title: isError ? '剪藏失败' : '剪藏成功', text: message, highlight: isError, timeout: 3000 }); } // --- 关键修改:使用 GM_xmlhttpRequest 的 clipPage 函数 --- async function clipPage() { const data = { title: document.title, url: window.location.href, html: document.documentElement.outerHTML }; try { // 使用 Promise 包装 GM_xmlhttpRequest 以便使用 async/await const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: settings.backendUrl, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(data), onload: (res) => { if (res.status >= 200 && res.status < 300) { resolve(res); } else { reject(new Error(`服务器返回错误: ${res.status} ${res.statusText}`)); } }, onerror: (err) => { reject(new Error(`网络请求失败: ${err}`)); } }); }); const result = JSON.parse(response.responseText); if (result.success) { showNotification(result.message || '页面已成功剪藏!', false); } else { showNotification(result.error || '剪藏操作失败。', true); } } catch (error) { console.error('Clipping error:', error); showNotification(`剪藏失败: ${error.message}`, true); } } // --- 主入口 --- function main() { loadSettings(); if (checkIfSiteIsActive()) { createFloatingButton(); createSettingsPanel(); } } main(); })();