Discourse Markdown 主题导出

将任意 Discourse 论坛的主题导出,支持动态页面加载

目前為 2024-10-27 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Discourse Markdown 主题导出
// @namespace    http://innjay.cn
// @version      1.1.0
// @description  将任意 Discourse 论坛的主题导出,支持动态页面加载
// @author       Hebaodan
// @match        http://*/*
// @match        https://*/*
// @license      MIT
// @icon         https://img.innjay.cn/i/2024/10/01/66fbf6914fe72.png
// @grant        GM_setClipboard
// @require      https://unpkg.com/[email protected]/dist/turndown.js
// @downloadURL
// @updateURL
// ==/UserScript==

(function() {
    'use strict';

    let buttonContainer = null;

    // 检查当前页面是否是 Discourse 论坛
    function isDiscourseForum() {
        return document.querySelector('meta[name="generator"][content*="Discourse"]') !== null;
    }

    // 检查是否在主题页面
    function isTopicPage() {
        return window.location.pathname.match(/\/t\/.*\/\d+/);
    }

    // 创建按钮容器
    function createButtonContainer() {
        if (buttonContainer) {
            buttonContainer.remove();
        }

        const container = document.createElement('div');
        container.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 9999;
            display: flex;
            flex-direction: column;
            gap: 10px;
        `;
        document.body.appendChild(container);
        buttonContainer = container;
        return container;
    }

    // 创建按钮
    function createButton(text, onClick) {
        const button = document.createElement('button');
        button.textContent = text;
        button.style.cssText = `
            padding: 10px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        `;
        button.addEventListener('click', onClick);
        return button;
    }

    // 获取文章内容
    function getArticleContent() {
        const titleElement = document.querySelector('#topic-title h1');
        const contentElement = document.querySelector('#post_1 .cooked');

        if (!titleElement || !contentElement) {
            console.error('无法找到文章标题或内容');
            return null;
        }

        return {
            title: titleElement.textContent.trim(),
            content: contentElement.innerHTML
        };
    }

    // 转换为Markdown
    function convertToMarkdown(article) {
        const turndownService = new TurndownService({
            headingStyle: 'atx',
            codeBlockStyle: 'fenced'
        });

        // 自定义规则处理图片和链接
        turndownService.addRule('images_and_links', {
            filter: ['a', 'img'],
            replacement: function (content, node) {
                // 处理图片
                if (node.nodeName === 'IMG') {
                    const alt = node.alt || '';
                    const src = node.getAttribute('src') || '';
                    const title = node.title ? ` "${node.title}"` : '';
                    return `![${alt}](${src}${title})`;
                }
                // 处理链接
                else if (node.nodeName === 'A') {
                    const href = node.getAttribute('href');
                    const title = node.title ? ` "${node.title}"` : '';
                    // 检查链接是否包含图片
                    const img = node.querySelector('img');
                    if (img) {
                        const alt = img.alt || '';
                        const src = img.getAttribute('src') || '';
                        const imgTitle = img.title ? ` "${img.title}"` : '';
                        return `[![${alt}](${src}${imgTitle})](${href}${title})`;
                    }
                    // 普通链接
                    return `[${node.textContent}](${href}${title})`;
                }
            }
        });

        return `# ${article.title}\n\n${turndownService.turndown(article.content)}`;
    }

    // 下载为Markdown文件
    function downloadAsMarkdown() {
        const article = getArticleContent();
        if (!article) {
            alert('无法获取文章内容,请检查网页结构是否变更。');
            return;
        }

        const markdown = convertToMarkdown(article);

        const blob = new Blob([markdown], { type: 'text/markdown;charset=utf-8' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `${article.title}.md`;
        a.click();
        URL.revokeObjectURL(url);

        showNotification('Markdown 文件已下载');
    }

    // 复制到剪贴板
    function copyToClipboard() {
        const article = getArticleContent();
        if (!article) {
            alert('无法获取文章内容,请检查网页结构是否变更。');
            return;
        }

        const markdown = convertToMarkdown(article);
        GM_setClipboard(markdown);

        showNotification('内容已复制到剪贴板');
    }

    // 显示通知
    function showNotification(message) {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background-color: #333;
            color: white;
            padding: 10px;
            border-radius: 5px;
            z-index: 10000;
        `;
        document.body.appendChild(notification);
        setTimeout(() => notification.remove(), 3000);
    }

    // 主函数
    function main() {
        // 首先检查是否是 Discourse 论坛
        if (!isDiscourseForum()) {
            return;
        }

        if (isTopicPage()) {
            const container = createButtonContainer();
            const downloadButton = createButton('下载 Markdown', downloadAsMarkdown);
            const copyButton = createButton('复制到剪贴板', copyToClipboard);
            container.appendChild(downloadButton);
            container.appendChild(copyButton);
        } else {
            if (buttonContainer) {
                buttonContainer.remove();
                buttonContainer = null;
            }
        }
    }

    // 延迟执行主函数,确保页面完全加载
    setTimeout(main, 1000);

    // 监听 URL 变化
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            setTimeout(main, 1000);
        }
    }).observe(document, { subtree: true, childList: true });
})();