// ==UserScript== // @name 海豚自动化工具 // @namespace http://tampermonkey.net/ // @version 1.3 // @description 自动从代码块复制内容到编辑器,保存并重启笔记本 // @author Kakune55 Carryc // @match https://labs.wd.dolphin-labs.com/data-analysis/* // @grant none // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // 通知管理器 - 管理多个通知的位置 const NotificationManager = { notifications: [], baseTop: 45, // 初始顶部位置 gap: 10, // 通知间距 add: function(element) { let topPosition = this.baseTop; this.notifications.forEach(notification => { if (notification.isConnected) { 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 { from { transform: rotate(0deg); } to { 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', 6000); }, 1000); } } }, 200); } // 查找所有目标代码块(包括“代码示例”和练习答案) function findAllCodeBlocks() { const textCellWraps = Array.from(document.querySelectorAll('.text-cell-wrap')); const allCodeBlocks = []; function findCodeBlock(container) { if (!container) return null; const codeBlock = container.querySelector('pre code'); return codeBlock || findCodeBlock(container.nextElementSibling); } textCellWraps.forEach((cellWrap, index) => { console.log(`处理容器 ${index + 1}:`, cellWrap); // 优先查找“代码示例”代码块 const exampleMarker = cellWrap.querySelector('.alert.alert-success.alertsuccess'); if (exampleMarker && exampleMarker.textContent.includes('代码示例')) { const codeBlock = findCodeBlock(cellWrap); if (codeBlock) { allCodeBlocks.push(codeBlock); console.log(`找到“代码示例”代码块内容:`, codeBlock.textContent.trim()); } else { console.warn(`未找到“代码示例”代码块!`); } } // 然后查找练习答案代码块 const collapseDiv = cellWrap.querySelector('.collapse'); if (collapseDiv) { const codeBlocks = collapseDiv.querySelectorAll('pre code'); if (codeBlocks.length > 0) { codeBlocks.forEach((codeBlock) => { allCodeBlocks.push(codeBlock); console.log(`找到练习答案代码块内容:`, codeBlock.textContent.trim()); }); } else { console.warn(`容器 ${index + 1} 中未找到练习答案代码块!`); } } }); return allCodeBlocks; } // 主函数:自动化流程 function runAutomation() { const button = document.getElementById('dolphin-auto-btn'); setButtonState(button, 'running'); // 修改按钮状态为“执行中” try { // 1. 查找所有目标代码块 const allCodeBlocks = findAllCodeBlocks(); console.log(`共找到 ${allCodeBlocks.length} 个有效代码块:`); allCodeBlocks.forEach((codeBlock, index) => { console.log(`代码块 ${index + 1}:`, codeBlock.textContent.trim()); }); // 2. 获取所有 CodeMirror 编辑器实例 const codeMirrors = Array.from(document.querySelectorAll('.CodeMirror')) .map(cmElement => cmElement.CodeMirror) .filter(cmInstance => !!cmInstance); console.log(`找到 ${codeMirrors.length} 个 CodeMirror 实例:`); if (allCodeBlocks.length === 0 || codeMirrors.length === 0) { throw new Error("未找到足够的代码块或编辑器!"); } // 3. 将代码块内容填充到 CodeMirror 编辑器 showNotification(`正在填充 ${Math.min(allCodeBlocks.length, codeMirrors.length)} 个代码块...`, 'info'); const minCount = Math.min(allCodeBlocks.length, codeMirrors.length); for (let i = 0; i < minCount; i++) { const codeText = allCodeBlocks[i].textContent.trim(); const cmInstance = codeMirrors[i]; if (cmInstance) { cmInstance.setValue(codeText); // 使用 CodeMirror API 填充内容 console.log(`已填充代码块 ${i + 1} 到 CodeMirror ${i + 1}`); } else { try { const textarea = codeMirrors[i].querySelector('textarea'); if (textarea) { textarea.value = codeText; // 使用替代方法填充内容 console.log(`使用替代方法填充代码块 ${i + 1}`); } } catch (e) { console.error(`无法填充代码块 ${i + 1}:`, e); } } } // 4. 检查多余的 CodeMirror 实例并通知用户 if (codeMirrors.length > allCodeBlocks.length) { const extraCount = codeMirrors.length - allCodeBlocks.length; console.warn(`多余的 CodeMirror 实例未被填充: 多出 ${extraCount} 个实例`); showNotification( `多余的 CodeMirror 实例未被填充: 多出 ${extraCount} 个实例,请检查最后是否有练习,手动填充`, 'warning', 10000 // 通知停留时间设置为 10 秒 ); } // 5. 点击保存按钮 console.log('准备点击保存按钮...'); showNotification('正在保存笔记本...', 'info'); const saveButton = findSaveButton(); if (!saveButton) { throw new Error("未找到保存按钮!"); } saveButton.click(); console.log('已点击保存按钮'); // 6. 等待保存成功后重启 waitForMessage(() => { console.log('准备点击重启按钮...'); showNotification('正在重启笔记本...', 'info'); const restartButton = findRestartButton(); if (!restartButton) { throw new Error("未找到重启按钮!"); } restartButton.click(); console.log('找到重启按钮,点击中...'); waitForRestartModal(); // 等待模态框确认 setButtonState(button, 'completed'); // 修改按钮状态为“已完成” }); } catch (error) { console.error(error.message); showNotification(`自动化执行失败:${error.message}`, 'error'); // 显示错误通知 setButtonState(button, 'failed'); // 修改按钮状态为“失败” } } // 辅助函数:查找保存按钮 function findSaveButton() { return document.querySelector('.btn-wrap .btn-name') || document.querySelector('[title="保存"]') || [...document.querySelectorAll('button')].find(btn => btn.textContent.includes('保存')); } // 辅助函数:查找重启按钮 function findRestartButton() { const menuItems = [...document.querySelectorAll('.ivu-dropdown-item, [role="menuitem"], .menu-item')]; const possibleTexts = ['重启 & 运行所有', '重启并运行所有', '重启&运行所有', '重启和运行所有']; return menuItems.find(item => { const text = item.textContent.trim(); return possibleTexts.some(pt => text.includes(pt)); }); } // 设置按钮状态 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'; setTimeout(() => setButtonState(button, 'idle'), 3000); } } // 显示美化的通知 function showNotification(message, type = 'info', duration = 3000) { 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 if (type === 'warning') { iconHTML = ' '; borderColor = '#FFA000'; } else { iconHTML = ' '; borderColor = '#2196F3'; } notification.innerHTML = iconHTML + `