【油猴脚本】一键复制网页全文为MarkDown格式文本到剪贴板

// ==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 ? '![' + alt + '](' + src + titlePart + ')' : '';
            }
        });

        // 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);
        }
    });
})();

评论

发表回复

人生梦想 - 关注前沿的计算机技术 acejoy.com 🐾 步子哥の博客 🐾 背多分论坛 🐾 知差(chai)网
快取状态: No
内存使用量: 10.115 MB
资料库查询次数: 21
页面产生时间: 0.233 (秒)