借一步网
作者:
在
// ==UserScript== // @name ToMD - Convert Content to Markdown // @namespace http://tampermonkey.net/ // @version 1.0 // @description Convert web page content to Markdown format and copy to clipboard // @author Steper Lin // @match *://*/* // @grant GM_addStyle // @grant GM_setClipboard // @require https://cdnjs.cloudflare.com/ajax/libs/turndown/7.1.1/turndown.min.js // ==/UserScript== (function() { 'use strict'; // Add styles for the button and notification GM_addStyle(` #tomd-button { position: fixed; bottom: 20px; right: 20px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; padding: 10px 15px; font-size: 14px; cursor: pointer; z-index: 9999; box-shadow: 0 2px 5px rgba(0,0,0,0.3); transition: all 0.3s ease; } #tomd-button:hover { background-color: #45a049; box-shadow: 0 3px 7px rgba(0,0,0,0.4); } #tomd-notification { position: fixed; bottom: 70px; right: 20px; background-color: #333; color: white; padding: 10px 15px; border-radius: 4px; font-size: 14px; z-index: 10000; opacity: 0; transition: opacity 0.3s ease; box-shadow: 0 2px 5px rgba(0,0,0,0.3); } #tomd-notification.show { opacity: 1; } `); // Create the ToMD button const button = document.createElement('button'); button.id = 'tomd-button'; button.textContent = 'ToMD'; document.body.appendChild(button); // Create notification element const notification = document.createElement('div'); notification.id = 'tomd-notification'; notification.textContent = 'Markdown copied to clipboard!'; document.body.appendChild(notification); // Function to show notification function showNotification(message = 'Markdown copied to clipboard!', duration = 2000) { notification.textContent = message; notification.classList.add('show'); setTimeout(() => { notification.classList.remove('show'); }, duration); } // Function to detect the main content of the page function detectMainContent() { // Try different strategies to find the main content // 1. Look for article tag let content = document.querySelector('article'); if (content && content.textContent.trim().length > 100) { return content; } // 2. Look for common content containers const contentSelectors = [ 'div.post-content', 'div.entry-content', 'div.content', 'div.post', 'div.article', 'div.main-content', 'main', '#content', '.content', '.post-body', '.post-content', '.article-content' ]; for (const selector of contentSelectors) { content = document.querySelector(selector); if (content && content.textContent.trim().length > 100) { return content; } } // 3. Heuristic approach: Find the element with the most text content const paragraphs = document.querySelectorAll('p'); if (paragraphs.length > 0) { // Find which container has the most paragraphs const containers = new Map(); paragraphs.forEach(p => { if (p.textContent.trim().length < 20) return; // Skip very short paragraphs // Find parent elements up to 4 levels let parent = p.parentElement; for (let i = 0; i < 4 && parent; i++) { containers.set(parent, (containers.get(parent) || 0) + 1); parent = parent.parentElement; } }); // Find the container with the most paragraphs let maxParagraphs = 0; let mainContainer = null; containers.forEach((count, container) => { if (count > maxParagraphs) { maxParagraphs = count; mainContainer = container; } }); if (mainContainer && maxParagraphs >= 3) { return mainContainer; } } // 4. Fallback to body if nothing else works return document.body; } // Function to convert HTML to Markdown function convertToMarkdown(element) { // Configure TurndownService for better markdown conversion const service = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced', emDelimiter: '*' }); // Improve list handling service.addRule('listItems', { filter: ['li'], replacement: function (content, node, options) { content = content.trim().replace(/^\s+|\s+$/g, ''); const isOrdered = node.parentNode.nodeName.toLowerCase() === 'ol'; const prefix = isOrdered ? '1. ' : '- '; const indent = node.parentNode.parentNode.nodeName.toLowerCase() === 'li' ? ' ' : ''; return indent + prefix + content; } }); // Improve image handling service.addRule('images', { filter: 'img', replacement: function (content, node) { const alt = node.alt || ''; const src = node.getAttribute('src') || ''; const title = node.title || ''; const titlePart = title ? ' "' + title + '"' : ''; return src ? '' : ''; } }); // Clone the element to avoid modifying the actual page const clonedElement = element.cloneNode(true); // Remove unnecessary elements from clone const elementsToRemove = [ 'script', 'style', 'iframe', 'nav', 'header:not(article header)', 'footer:not(article footer)', '.sidebar', '.comments', '.navigation', '.ads', '.share-buttons' ]; elementsToRemove.forEach(selector => { const elements = clonedElement.querySelectorAll(selector); elements.forEach(el => el.parentNode?.removeChild(el)); }); // Convert the HTML to markdown return service.turndown(clonedElement.innerHTML); } // Button click handler button.addEventListener('click', function() { try { // Find the main content const mainContent = detectMainContent(); if (!mainContent) { showNotification('Could not detect main content on this page.', 3000); return; } // Convert content to Markdown const markdown = convertToMarkdown(mainContent); if (!markdown || markdown.trim().length === 0) { showNotification('No content found to convert.', 3000); return; } // Copy to clipboard GM_setClipboard(markdown); // Show success notification showNotification('Markdown copied to clipboard!'); } catch (error) { console.error('ToMD Error:', error); showNotification('Error converting to Markdown. See console for details.', 4000); } }); })();
要发表评论,您必须先登录。