Greasy Fork is available in English.
划词选中后弹出优美悬浮按钮,一键导出为 Markdown(支持丰富格式保留)。v4.3 已彻底解决按钮不显示问题,兼容各类广告屏蔽脚本。
// ==UserScript==
// @name 网页选中内容一键导出 Markdown(最终正式版 v4.3)
// @namespace http://tampermonkey.net/
// @version 4.3
// @description 划词选中后弹出优美悬浮按钮,一键导出为 Markdown(支持丰富格式保留)。v4.3 已彻底解决按钮不显示问题,兼容各类广告屏蔽脚本。
// @author Gemini & xc(优化 by Grok)
// @license GPL-3.0
// @match *://*/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 1. 注入少量 CSS(仅负责动画和悬停效果)
const style = document.createElement('style');
style.textContent = `
#grok-md-export-btn {
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55) !important;
}
#grok-md-export-btn.show {
animation: grokPop 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55) forwards !important;
}
@keyframes grokPop {
0% { opacity: 0; transform: translateY(20px) scale(0.85); }
100% { opacity: 1; transform: translateY(0) scale(1); }
}
#grok-md-export-btn:hover {
transform: translateY(-3px) scale(1.05) !important;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3) !important;
}
`;
document.head.appendChild(style);
// 2. 创建按钮(关键样式全部内联,确保不被其他脚本覆盖)
const exportBtn = document.createElement('div');
exportBtn.id = 'grok-md-export-btn';
exportBtn.innerHTML = `<span style="font-size:20px;">✨</span> 导出 Markdown`;
exportBtn.style.cssText = `
position: fixed !important;
z-index: 2147483647 !important;
opacity: 0;
visibility: hidden;
transform: translateY(20px) scale(0.85);
background: rgba(30, 30, 32, 0.92) !important;
backdrop-filter: blur(16px) !important;
-webkit-backdrop-filter: blur(16px) !important;
color: #ffffff !important;
padding: 12px 24px !important;
border-radius: 16px !important;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif !important;
font-size: 15.5px !important;
font-weight: 600 !important;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25) !important;
display: flex !important;
align-items: center !important;
gap: 9px !important;
white-space: nowrap !important;
cursor: pointer !important;
user-select: none !important;
pointer-events: auto !important;
border: 1px solid rgba(255,255,255,0.15) !important;
`;
document.body.appendChild(exportBtn);
let currentExportContent = '';
// 3. Markdown 解析函数(智能保留格式)
function nodeToMarkdown(node) {
if (node.nodeType === Node.TEXT_NODE) return node.textContent;
if (node.nodeType !== Node.ELEMENT_NODE) return '';
const tag = node.tagName.toLowerCase();
let inner = Array.from(node.childNodes).map(nodeToMarkdown).join('');
switch (tag) {
case 'h1': return `# ${inner.trim()}\n\n`;
case 'h2': return `## ${inner.trim()}\n\n`;
case 'h3': return `### ${inner.trim()}\n\n`;
case 'h4': return `#### ${inner.trim()}\n\n`;
case 'h5': return `##### ${inner.trim()}\n\n`;
case 'h6': return `###### ${inner.trim()}\n\n`;
case 'p': case 'div': case 'section': case 'article': return `${inner.trim()}\n\n`;
case 'strong': case 'b': return `**${inner.trim()}**`;
case 'em': case 'i': return `*${inner.trim()}*`;
case 'a': {
let href = node.getAttribute('href') || '';
if (href) {
if (!href.startsWith('http')) href = href.startsWith('/') ? window.location.origin + href : new URL(href, window.location.href).href;
return `[${inner.trim()}](${href})`;
}
return inner.trim();
}
case 'ul': {
let list = '\n';
Array.from(node.children).forEach(child => { if (child.tagName.toLowerCase() === 'li') list += `- ${nodeToMarkdown(child).trim()}\n`; });
return list + '\n';
}
case 'ol': {
let list = '\n';
Array.from(node.children).forEach((child, i) => { if (child.tagName.toLowerCase() === 'li') list += `${i + 1}. ${nodeToMarkdown(child).trim()}\n`; });
return list + '\n';
}
case 'li': return inner.trim();
case 'img': {
let src = node.getAttribute('src') || node.getAttribute('data-src') || node.getAttribute('data-original') || '';
if (src) {
if (src.startsWith('//')) src = 'https:' + src;
else if (!src.startsWith('http')) src = new URL(src, window.location.href).href;
return `\n\n`;
}
return '';
}
case 'blockquote': return `> ${inner.replace(/\n/g, '\n> ')}\n\n`;
case 'pre': return `\n\`\`\`\n${inner.trim()}\n\`\`\`\n\n`;
case 'code': return node.parentElement?.tagName.toLowerCase() === 'pre' ? inner : `\`${inner.trim()}\``;
case 'br': return '\n';
default: return inner.trim() ? inner.trim() + '\n\n' : '';
}
}
function parseSelection() {
const sel = window.getSelection();
if (sel.rangeCount === 0 || sel.toString().trim() === '') return null;
const range = sel.getRangeAt(0);
const frag = range.cloneContents();
const tempDiv = document.createElement('div');
tempDiv.appendChild(frag);
let mdContent = sel.toString().trim();
try {
mdContent = nodeToMarkdown(tempDiv).replace(/(\n{3,})/g, '\n\n').trim();
} catch(e) {}
const metaData = `# [${document.title}](${window.location.href})\n> **抓取时间:** ${new Date().toLocaleString('zh-CN')}\n\n---\n\n`;
return metaData + mdContent;
}
// 4. 鼠标事件
document.addEventListener('mouseup', function(e) {
if (e.target === exportBtn || exportBtn.contains(e.target)) return;
setTimeout(() => {
currentExportContent = parseSelection();
if (currentExportContent) {
let left = e.clientX + 25;
let top = e.clientY + 25;
const btnWidth = 200;
const btnHeight = 55;
if (left + btnWidth > window.innerWidth) left = e.clientX - btnWidth - 25;
if (top + btnHeight > window.innerHeight) top = e.clientY - btnHeight - 35;
exportBtn.style.left = `${left}px`;
exportBtn.style.top = `${top}px`;
exportBtn.style.opacity = '1';
exportBtn.style.visibility = 'visible';
exportBtn.style.transform = 'translateY(0) scale(1)';
exportBtn.classList.add('show');
}
}, 80);
});
document.addEventListener('mousedown', () => {
exportBtn.style.opacity = '0';
exportBtn.style.visibility = 'hidden';
exportBtn.classList.remove('show');
});
// 5. 点击导出 + 成功反馈
exportBtn.addEventListener('click', function(e) {
e.stopPropagation();
if (!currentExportContent) return;
const blob = new Blob([currentExportContent], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
const shortTitle = document.title.substring(0, 25).replace(/[/\\?%*:|"<>]/g, '_');
a.href = url;
a.download = `摘录_${shortTitle}_${Date.now()}.md`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
// 成功反馈
const originalHTML = exportBtn.innerHTML;
exportBtn.style.background = 'rgba(16, 185, 129, 0.95) !important';
exportBtn.innerHTML = `<span style="font-size:20px;">✅</span> 已成功导出!`;
setTimeout(() => {
exportBtn.style.opacity = '0';
exportBtn.style.visibility = 'hidden';
exportBtn.style.background = 'rgba(30, 30, 32, 0.92) !important';
exportBtn.innerHTML = originalHTML;
exportBtn.classList.remove('show');
window.getSelection().removeAllRanges();
currentExportContent = '';
}, 1600);
});
})();