// ==UserScript== // @name 海豚自动化工具 // @namespace http://tampermonkey.net/ // @version 1.2 // @description 自动从代码块复制内容到编辑器,保存并重启笔记本 // @author Kakune55 Carryc // @match https://labs.wd.dolphin-labs.com/data-analysis/* // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 通知管理器 - 管理多个通知的位置 const NotificationManager = { notifications: [], baseTop: 70, // 初始顶部位置 gap: 10, // 通知间距 // 添加通知到管理器 add: function(element) { // 计算新通知的顶部位置 let topPosition = this.baseTop; // 为已有的每个通知增加高度和间隔 this.notifications.forEach(notification => { if (notification.isConnected) { // 确保通知仍在DOM中 const height = notification.offsetHeight; topPosition += height + this.gap; } }); // 过滤掉已移除的通知 this.notifications = this.notifications.filter(n => n.isConnected); // 设置新通知位置 element.style.top = topPosition + 'px'; // 添加到管理器 this.notifications.push(element); return element; }, // 从管理器移除通知 remove: function(element) { const index = this.notifications.indexOf(element); if (index > -1) { this.notifications.splice(index, 1); } // 重新计算剩余通知的位置 this.updatePositions(); }, // 更新所有通知的位置 updatePositions: function() { let topPosition = this.baseTop; this.notifications.forEach(notification => { if (notification.isConnected) { notification.style.top = topPosition + 'px'; topPosition += notification.offsetHeight + this.gap; } }); } }; // 添加样式到页面 function addStyles() { const styleSheet = document.createElement('style'); styleSheet.textContent = ` @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } @keyframes fadeIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } @keyframes fadeOut { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-20px); } } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes ripple { to { transform: scale(3); opacity: 0; } } #dolphin-auto-btn { animation: slideInRight 0.5s ease-out; } .dolphin-notification { animation: fadeIn 0.3s ease-out; display: flex; align-items: center; border-left: 4px solid; } .dolphin-notification.hiding { animation: fadeOut 0.5s ease-in forwards; } .notification-icon { margin-right: 12px; font-size: 20px; } .loader { display: inline-block; width: 15px; height: 15px; border: 2px solid rgba(255,255,255,0.3); border-radius: 50%; border-top-color: #fff; animation: spin 0.8s linear infinite; margin-right: 8px; } `; document.head.appendChild(styleSheet); } // 等待保存成功消息 function waitForMessage(callback) { const messageDiv = document.querySelector('.ivu-message'); if (messageDiv && messageDiv.textContent.includes('保存成功')) { console.log('保存成功'); callback(); } else { setTimeout(() => waitForMessage(callback), 100); } } // 等待模态框并点击确认 function waitForRestartModal() { console.log('等待重启确认模态框...'); const checkModal = setInterval(() => { const modals = [...document.querySelectorAll('.ivu-modal')]; const targetModal = modals.find(modal => modal.textContent.includes('重新启动内核并重新运行整个笔记本')); if (targetModal) { const confirmButton = targetModal.querySelector('.ivu-btn-primary'); if (confirmButton) { confirmButton.click(); console.log('已点击"确定"按钮'); clearInterval(checkModal); // 执行完成后通知用户 setTimeout(() => { showNotification('自动化执行完成:代码已复制、保存并重启', 'success'); }, 1000); } } }, 200); } // 提取代码文本 function extractCodeText(codeBlock) { return codeBlock.innerText || [...codeBlock.querySelectorAll('span')].reduce((text, span) => text + span.textContent, ''); } // 显示美化的通知 function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = 'dolphin-notification'; let iconHTML = ''; let borderColor = ''; // 根据类型设置不同的图标和颜色 if (type === 'success') { iconHTML = '
'; borderColor = '#4CAF50'; } else if (type === 'error') { iconHTML = '
'; borderColor = '#F44336'; } else { iconHTML = '
ℹ️
'; borderColor = '#2196F3'; } notification.innerHTML = iconHTML + `
${message}
`; notification.style.cssText = ` position: fixed; right: 300px; padding: 15px 20px; background: white; color: #333; border-radius: 6px; box-shadow: 0 4px 15px rgba(0,0,0,0.15); z-index: 10000; max-width: 350px; font-size: 14px; border-left-color: ${borderColor}; display: flex; align-items: center; transition: top 0.3s ease; `; document.body.appendChild(notification); // 通过通知管理器添加并定位通知 NotificationManager.add(notification); // 鼠标悬停时停止自动隐藏 let timeoutId; notification.addEventListener('mouseenter', () => { clearTimeout(timeoutId); }); notification.addEventListener('mouseleave', () => { startHideTimeout(); }); function startHideTimeout() { timeoutId = setTimeout(() => { notification.classList.add('hiding'); setTimeout(() => { notification.remove(); // 从管理器中移除并重新排列其他通知 NotificationManager.remove(notification); }, 500); }, 3000); } // 开始隐藏倒计时 startHideTimeout(); // 添加点击关闭功能 notification.addEventListener('click', () => { clearTimeout(timeoutId); notification.classList.add('hiding'); setTimeout(() => { notification.remove(); NotificationManager.remove(notification); }, 500); }); return notification; } // 设置按钮状态 function setButtonState(button, state) { if (!button) return; // 定义不同状态的样式 const states = { idle: { text: '运行自动化', backgroundColor: '#2196F3', disabled: false, html: '运行自动化' }, running: { text: '执行中', backgroundColor: '#FFA000', disabled: true, html: '
执行中' }, completed: { text: '已完成', backgroundColor: '#4CAF50', disabled: true, html: '✅ 已完成' }, failed: { text: '执行失败', backgroundColor: '#F44336', disabled: false, html: '❌ 重试' } }; const newState = states[state]; // 应用过渡效果 button.style.transition = 'all 0.3s ease'; button.disabled = newState.disabled; button.innerHTML = newState.html; button.style.backgroundColor = newState.backgroundColor; // 如果是完成状态,添加脉冲动画 if (state === 'completed') { button.style.animation = 'pulse 0.5s ease-in-out'; // 3秒后恢复到初始状态 setTimeout(() => setButtonState(button, 'idle'), 3000); } } // 主函数 function runAutomation() { // 修改按钮状态 const button = document.getElementById('dolphin-auto-btn'); setButtonState(button, 'running'); // 1. 查找代码块 let codeBlocks = document.querySelectorAll('pre > code.cm-s-ipython[class*="language-"]') || document.querySelectorAll('pre > code[class*="language-"]') || document.querySelectorAll('pre > code'); console.log(`找到 ${codeBlocks.length} 个代码块`); // 2. 查找编辑器 const codeMirrors = document.querySelectorAll('.CodeMirror'); console.log(`找到 ${codeMirrors.length} 个编辑器`); if (codeBlocks.length > 0 && codeMirrors.length > 0) { showNotification(`正在填充 ${Math.min(codeBlocks.length, codeMirrors.length)} 个代码块...`, 'info'); // 3. 将代码块内容复制到编辑器 codeBlocks.forEach((codeBlock, index) => { if (index < codeMirrors.length) { const codeText = extractCodeText(codeBlock); const cm = codeMirrors[index].CodeMirror; if (cm) { cm.setValue(codeText); console.log(`已填充代码块 ${index + 1}`); } else { try { const textarea = codeMirrors[index].querySelector('textarea'); if (textarea) { textarea.value = codeText; console.log(`使用替代方法填充代码块 ${index + 1}`); } } catch (e) { console.error(`无法填充代码块 ${index + 1}:`, e); } } } }); // 4. 点击保存按钮 console.log('准备点击保存按钮...'); showNotification('正在保存笔记本...', 'info'); const saveButton = document.querySelector('.btn-wrap .btn-name') || document.querySelector('[title="保存"]') || [...document.querySelectorAll('button')].find(btn => btn.textContent.includes('保存')); if (saveButton) { saveButton.click(); console.log('已点击保存按钮'); // 5. 等待保存成功后重启 waitForMessage(() => { console.log('准备点击重启按钮...'); showNotification('正在重启笔记本...', 'info'); const menuItems = [...document.querySelectorAll('.ivu-dropdown-item, [role="menuitem"], .menu-item')]; const possibleTexts = ['重启 & 运行所有', '重启并运行所有', '重启&运行所有', '重启和运行所有']; const restartButton = menuItems.find(item => { const text = item.textContent.trim(); return possibleTexts.some(pt => text.includes(pt)); }); if (restartButton) { console.log('找到重启按钮,点击中...'); restartButton.click(); waitForRestartModal(); setButtonState(button, 'completed'); } else { console.error('未找到重启按钮!'); console.log('可用菜单项:'); menuItems.forEach((item, i) => console.log(`${i+1}. "${item.textContent.trim()}"`)); // 执行遇到错误,通知用户 showNotification('自动化执行失败:未找到重启按钮!', 'error'); setButtonState(button, 'failed'); } }); } else { console.error('未找到保存按钮!'); // 执行遇到错误,通知用户 showNotification('自动化执行失败:未找到保存按钮!', 'error'); setButtonState(button, 'failed'); } } else { console.error("未找到足够的代码块或编辑器!"); // 执行遇到错误,通知用户 showNotification('自动化执行失败:未找到足够的代码块或编辑器!', 'error'); setButtonState(button, 'failed'); } } // 创建悬浮按钮 function createFloatingButton() { const button = document.createElement('button'); button.id = 'dolphin-auto-btn'; button.innerHTML = '运行自动化'; button.style.cssText = ` position: fixed; top: 20px; right: 300px; /* 距离右侧300像素 */ z-index: 10000; padding: 12px 20px; background-color: #2196F3; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; box-shadow: 0 3px 10px rgba(33, 150, 243, 0.3); transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; min-width: 140px; font-weight: 600; `; // 鼠标悬停效果,缓慢放大并加深颜色 button.addEventListener('mouseover', () => { button.style.transform = 'translateY(-2px)'; button.style.boxShadow = '0 5px 15px rgba(33, 150, 243, 0.4)'; button.style.backgroundColor = '#1976D2'; }); button.addEventListener('mouseout', () => { button.style.transform = ''; button.style.boxShadow = '0 3px 10px rgba(33, 150, 243, 0.3)'; if (!button.disabled) { button.style.backgroundColor = '#2196F3'; } }); // 添加点击波纹效果 button.addEventListener('mousedown', (e) => { const ripple = document.createElement('span'); const rect = button.getBoundingClientRect(); const size = Math.max(rect.width, rect.height); const x = e.clientX - rect.left - size / 2; const y = e.clientY - rect.top - size / 2; ripple.style.cssText = ` position: absolute; top: ${y}px; left: ${x}px; width: ${size}px; height: ${size}px; background-color: rgba(255, 255, 255, 0.7); border-radius: 50%; transform: scale(0); animation: ripple 0.6s linear; pointer-events: none; `; button.appendChild(ripple); setTimeout(() => { ripple.remove(); }, 600); }); // 点击事件 button.addEventListener('click', () => { if (!button.disabled) { runAutomation(); } }); document.body.appendChild(button); return button; } // 初始化函数 function init() { addStyles(); const button = createFloatingButton(); // 显示欢迎通知 setTimeout(() => { showNotification('海豚自动化工具已加载,点击按钮开始自动化操作', 'info'); }, 1000); } // 页面加载后初始化 setTimeout(init, 1000); })();