// ==UserScript== // @name 复制格式转换 (Markdown) // @namespace http://tampermonkey.net/ // @version 1.2 // @description 选中内容后浮现按钮在右上角,点击按钮自动复制为完整的 Markdown 格式,并确保排版正确,包括对数学公式的处理。 // @author KiwiFruit // @match *://*/* // @grant GM_setClipboard // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; // 配置选项 const config = { preserveEmptyLines: false, // 是否保留空行 addSeparators: true, // 是否自动添加分隔符 }; // 创建浮动按钮 function createFloatingButton() { const button = document.createElement('button'); button.id = 'markdownCopyButton'; button.innerText = '复制为 Markdown 格式 (Ctrl+Shift+C)'; button.style.position = 'fixed'; button.style.top = '20px'; button.style.right = '20px'; button.style.padding = '10px 20px'; button.style.backgroundColor = '#007bff'; button.style.color = '#fff'; button.style.border = 'none'; button.style.borderRadius = '5px'; button.style.cursor = 'pointer'; button.style.zIndex = '9999'; button.style.display = 'none'; button.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)'; document.body.appendChild(button); return button; } let floatingButton = createFloatingButton(); // 创建预览窗口 function createPreviewWindow() { const preview = document.createElement('div'); preview.id = 'markdownPreview'; preview.style.position = 'fixed'; preview.style.top = '60px'; preview.style.right = '20px'; preview.style.width = '300px'; preview.style.maxHeight = '400px'; preview.style.overflowY = 'auto'; preview.style.padding = '10px'; preview.style.backgroundColor = '#fff'; preview.style.border = '1px solid #ddd'; preview.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)'; preview.style.zIndex = '9999'; preview.style.display = 'none'; document.body.appendChild(preview); return preview; } let previewWindow = createPreviewWindow(); // 显示浮动按钮 function showFloatingButton() { floatingButton.style.display = 'block'; } // 隐藏浮动按钮 function hideFloatingButton() { floatingButton.style.display = 'none'; previewWindow.style.display = 'none'; // 同时隐藏预览窗口 } // 递归提取并转换为 Markdown function convertToMarkdown(node) { if (node.nodeType === Node.TEXT_NODE) { const text = node.textContent.trim(); return config.preserveEmptyLines ? text : text || ''; } if (node.nodeType === Node.ELEMENT_NODE) { switch (node.tagName.toLowerCase()) { case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': { const level = node.tagName.toLowerCase().replace('h', ''); const content = processChildren(node); return `${'#'.repeat(parseInt(level))} ${content} `; } case 'p': { return `${processChildren(node)} `; } case 'ul': { let markdown = ''; for (const li of node.children) { markdown += `- ${processChildren(li)} `; } return markdown + '\n'; } case 'ol': { let markdown = ''; let index = 1; for (const li of node.children) { markdown += `${index++}. ${processChildren(li)} `; } return markdown + '\n'; } case 'blockquote': { const content = processChildren(node); return `> ${content.split('\n').join('\n> ')} `; } case 'pre': { return `\`\`\` ${node.textContent.trim()} \`\`\` `; } case 'code': { return `\`${node.textContent.trim()}\``; } case 'a': { const text = processChildren(node); const href = node.href || ''; return `[${text}](${href})`; } case 'img': { const alt = node.alt || 'Image'; const src = node.src || ''; return `![${alt}](${src})`; } case 'strong': case 'b': { return `**${processChildren(node)}**`; } case 'em': case 'i': { return `*${processChildren(node)}*`; } case 'del': { return `~~${processChildren(node)}~~`; } case 'hr': { return '---\n'; } case 'table': { const rows = Array.from(node.rows).map(row => { const cells = Array.from(row.cells).map(cell => `| ${processChildren(cell).trim()} `); return cells.join('') + '|'; }); return `${rows.join('\n')} `; } case 'span': { // 新增:处理数学公式 if (node.classList.contains('math')) { const formula = node.textContent.trim(); return `$$${formula}$$`; // 假设所有公式都是块级公式 } break; } default: { return processChildren(node); } } } return ''; // 忽略其他类型的节点 } // 辅助函数:处理子节点 function processChildren(node) { let result = ''; for (const child of node.childNodes) { result += convertToMarkdown(child); } return result.trim(); } // 提取选中内容并转换为 Markdown function extractAndConvertToMarkdown(range) { return Array.from(range.cloneContents().childNodes) .map(node => convertToMarkdown(node)) .join('') .trim(); } // 复制到剪贴板 async function copyToClipboard(text) { try { if (typeof GM_setClipboard === 'function') { GM_setClipboard(text); // 使用 Tampermonkey 提供的剪贴板功能 return true; } else { await navigator.clipboard.writeText(text); console.log('Markdown 已复制到剪贴板'); return true; } } catch (err) { console.error('复制失败:', err); return false; } } // 显示提示信息 function showToast(message) { const toast = document.createElement('div'); toast.style.position = 'fixed'; toast.style.bottom = '20px'; toast.style.right = '20px'; toast.style.padding = '10px 20px'; toast.style.backgroundColor = '#333'; toast.style.color = '#fff'; toast.style.borderRadius = '5px'; toast.style.zIndex = '9999'; toast.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)'; toast.innerText = message; document.body.appendChild(toast); setTimeout(() => { toast.remove(); }, 2000); } // 监听鼠标释放事件 document.addEventListener('mouseup', handleMouseUp); // 处理鼠标释放事件 function handleMouseUp(event) { const selection = window.getSelection(); if (selection && !selection.isCollapsed) { showFloatingButton(); floatingButton.onclick = async () => { try { const range = selection.getRangeAt(0); const markdownContent = extractAndConvertToMarkdown(range); previewWindow.innerText = markdownContent; // 显示预览 previewWindow.style.display = 'block'; const success = await copyToClipboard(markdownContent); if (success) { showToast('内容已复制为 Markdown 格式!'); } else { showToast('复制失败,请重试!'); } } catch (err) { console.error('处理内容时出错:', err); showToast(`发生错误:${err.message}`); } finally { hideFloatingButton(); } }; } else { hideFloatingButton(); } } // 添加快捷键支持 document.addEventListener('keydown', (event) => { if (event.ctrlKey && event.shiftKey && event.code === 'KeyC') { handleMouseUp(event); // 模拟鼠标释放事件 } }); // 监听页面点击事件,点击空白区域时隐藏按钮 document.addEventListener('mousedown', (event) => { if (!floatingButton.contains(event.target) && !previewWindow.contains(event.target)) { hideFloatingButton(); } }); })();