Dedao article2markdown

Convert Dedao articles to Markdown format

// ==UserScript==
// @name         Dedao article2markdown
// @namespace    http://tampermonkey.net/
// @version      2025-06-02
// @description  Convert Dedao articles to Markdown format
// @author       AA
// @match        https://www.dedao.cn/course/**
// @icon         https://www.google.com/s2/favicons?sz=64&domain=dedao.cn
// @grant        GM_setClipboard
// @grant        GM_notification
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    function htmlToMarkdown(htmlElement) {
        let markdown = '';

        // Process each child node
        for (const child of htmlElement.childNodes) {
            if (child.nodeType === Node.ELEMENT_NODE) {
                // Handle headers
                if (child.classList.contains('header-2')) {
                    markdown += '### ' + child.textContent.trim() + '\n';
                }
                else if (child.classList.contains('header-3')) {
                    markdown += '#### ' + child.textContent.trim() + '\n';
                }
                else if (child.tagName === 'P') {
                    // 处理段落内容,包括<b>标签
                    let paragraphText = '';
                    for (const node of child.childNodes) {
                        if (node.nodeType === Node.TEXT_NODE) {
                            paragraphText += node.textContent;
                        } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'B') {
                            paragraphText += ' **' + node.textContent + '** ';
                        }
                    }

                    // 检查<p>是否只包含<br>或空白
                    const hasOnlyBr = child.children.length === 1 &&
                                     child.children[0].tagName === 'BR' &&
                                     child.textContent.trim() === '';

                    if (hasOnlyBr) {
                        // 对于只包含<br>的<p>,转换为单个换行
                        markdown += '\n\n';
                    } else {
                        // 普通段落处理
                        const text = paragraphText.trim();
                        if (text) {
                            markdown += text + '\n';
                        } else {
                            // 完全空的段落
                            markdown += '\n';
                        }
                    }
                }
                else if (child.classList.contains('original-block-quote')) {
                    // Blockquote
                    const quoteText = child.querySelector('blockquote').textContent.trim();
                    markdown += '> ' + quoteText.replace(/\n/g, '\n> ') + '\n\n';
                }
                // 直接处理独立的<b>标签(非段落内)
                else if (child.tagName === 'B') {
                    markdown += '**' + child.textContent + '**';
                }
            }
        }

        // Clean up multiple consecutive newlines
        markdown = markdown.replace(/\n{3,}/g, '\n\n');
        return markdown.trim();
    }


    function showMarkdownModal(markdown) {
        // Create modal container
        const modal = document.createElement('div');
        modal.style.position = 'fixed';
        modal.style.top = '50px';
        modal.style.left = '50%';
        modal.style.transform = 'translateX(-50%)';
        modal.style.width = '80%';
        modal.style.maxWidth = '800px';
        modal.style.maxHeight = '80vh';
        modal.style.backgroundColor = 'white';
        modal.style.border = '1px solid #ddd';
        modal.style.borderRadius = '5px';
        modal.style.boxShadow = '0 4px 8px rgba(0,0,0,0.2)';
        modal.style.zIndex = '9999';
        modal.style.padding = '20px';
        modal.style.overflow = 'auto';

        // Create title
        const title = document.createElement('h3');
        title.textContent = 'Markdown Conversion';
        title.style.marginTop = '0';
        modal.appendChild(title);

        // Create code block
        const pre = document.createElement('pre');
        pre.style.backgroundColor = '#f5f5f5';
        pre.style.padding = '15px';
        pre.style.borderRadius = '4px';
        pre.style.overflow = 'auto';
        pre.style.whiteSpace = 'pre-wrap';

        const code = document.createElement('code');
        code.textContent = markdown;
        pre.appendChild(code);
        modal.appendChild(pre);

        // Create copy button
        const copyBtn = document.createElement('button');
        copyBtn.textContent = 'Copy to Clipboard';
        copyBtn.style.marginTop = '10px';
        copyBtn.style.padding = '8px 15px';
        copyBtn.style.backgroundColor = '#4CAF50';
        copyBtn.style.color = 'white';
        copyBtn.style.border = 'none';
        copyBtn.style.borderRadius = '4px';
        copyBtn.style.cursor = 'pointer';

        copyBtn.addEventListener('click', () => {
            navigator.clipboard.writeText(markdown).then(() => {
                GM_notification({
                    title: 'Copied!',
                    text: 'Markdown content copied to clipboard',
                    timeout: 2000
                });
            }).catch(err => {
                console.error('Failed to copy:', err);
            });
        });

        modal.appendChild(copyBtn);

        // Create close button
        const closeBtn = document.createElement('button');
        closeBtn.textContent = 'Close';
        closeBtn.style.marginLeft = '10px';
        closeBtn.style.padding = '8px 15px';
        closeBtn.style.backgroundColor = '#f44336';
        closeBtn.style.color = 'white';
        closeBtn.style.border = 'none';
        closeBtn.style.borderRadius = '4px';
        closeBtn.style.cursor = 'pointer';

        closeBtn.addEventListener('click', () => {
            document.body.removeChild(modal);
        });

        modal.appendChild(closeBtn);

        // Add modal to document
            document.body.appendChild(modal);
        }

        // Wait for content to load

    const convertBtn = document.createElement('button');
    convertBtn.innerHTML = '转换为<br>Markdown';
    convertBtn.style.position = 'fixed';
    convertBtn.style.top = '5vh';
    convertBtn.style.right = '20px';
    convertBtn.style.zIndex = '9999';
    convertBtn.style.padding = '8px 16px';
    convertBtn.style.backgroundColor = '#4CAF50';
    convertBtn.style.color = 'white';
    convertBtn.style.border = 'none';
    convertBtn.style.borderRadius = '4px';
    convertBtn.style.cursor = 'pointer';
    convertBtn.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
    convertBtn.style.fontSize = '14px';

    // 添加悬停效果
    convertBtn.addEventListener('mouseover', () => {
        convertBtn.style.backgroundColor = '#45a049';
    });
    convertBtn.addEventListener('mouseout', () => {
        convertBtn.style.backgroundColor = '#4CAF50';
    });

    // 按钮点击事件处理
    convertBtn.addEventListener('click', () => {
        const editorShow = document.querySelector('.editor-show');
        if (editorShow) {
            const markdown = htmlToMarkdown(editorShow);
            showMarkdownModal(markdown);
        } else {
            GM_notification({
                title: '转换失败',
                text: '未找到文章内容,请确认是否在文章页面',
                timeout: 3000
            });
        }
    });


    // 将按钮添加到页面

    setTimeout(() => {
        document.body.appendChild(convertBtn);
    }, 1000);
})();