Greasy Fork

Greasy Fork is available in English.

知识星球文章去水印、复制为 Markdown

知识星球辅助,文章去水印、可手动复制,可以一键复制为 Markdown

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         知识星球文章去水印、复制为 Markdown
// @namespace    http://tampermonkey.net/
// @version      0.0.1
// @description  知识星球辅助,文章去水印、可手动复制,可以一键复制为 Markdown
// @author       Loner1024
// @match        https://articles.zsxq.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=zsxq.com
// @require      https://cdnjs.cloudflare.com/ajax/libs/turndown/7.1.1/turndown.min.js
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @license MIT
// ==/UserScript==

(function() {
    'use strict';
    let body = document.getElementsByTagName('body')[0];
    body.classList.remove("js-disable-copy");
    let post = document.getElementsByClassName('post')[0];
    post.style.removeProperty('background-image');
    post.style.removeProperty('background-repeat');
    post.style.removeProperty("background-size");

      // 添加样式
    GM_addStyle(`
        #md-copy-btn {
            position: fixed;
            bottom: 20px;
            right: 20px;
            padding: 10px 15px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            z-index: 9999;
            font-family: Arial, sans-serif;
            font-size: 14px;
        }
        #md-copy-btn:hover {
            background-color: #45a049;
        }
        #md-status {
            position: fixed;
            bottom: 70px;
            right: 20px;
            padding: 8px 12px;
            background-color: rgba(0,0,0,0.7);
            color: white;
            border-radius: 5px;
            font-family: Arial, sans-serif;
            font-size: 14px;
            z-index: 9999;
            display: none;
        }
    `);

    // 创建复制按钮
    function createCopyButton() {
        const button = document.createElement('button');
        button.id = 'md-copy-btn';
        button.textContent = '复制文章为Markdown';
        button.addEventListener('click', extractAndCopyArticle);

        const status = document.createElement('div');
        status.id = 'md-status';
        status.textContent = '';

        document.body.appendChild(button);
        document.body.appendChild(status);
    }

    // 显示状态消息
    function showStatus(message, isError = false) {
        const status = document.getElementById('md-status');
        status.textContent = message;
        status.style.backgroundColor = isError ? 'rgba(255,0,0,0.7)' : 'rgba(0,0,0,0.7)';
        status.style.display = 'block';

        setTimeout(() => {
            status.style.display = 'none';
        }, 3000);
    }

    // 在转换前预处理HTML,保留空白
    // 在turndown处理前运行此函数
    function preserveCodeIndentation() {
        // 找到所有代码块容器
        const codeContainers = document.querySelectorAll('.ql-code-block-container');

        codeContainers.forEach(container => {
            // 处理每个代码行
            const codeBlocks = container.querySelectorAll('.ql-code-block');

            codeBlocks.forEach(block => {
                // 获取原始HTML
                const originalHtml = block.innerHTML;

                // 检查是否有前导空格
                const leadingWhitespaceMatch = originalHtml.match(/^(\s| )+/);

                if (leadingWhitespaceMatch) {
                    // 计算空格数量
                    const whitespace = leadingWhitespaceMatch[0]
                    .replace(/ /g, ' ')
                    .replace(/\s/g, ' ');

                    // 创建用于显示空格的元素
                    const spacesSpan = document.createElement('span');
                    spacesSpan.className = 'preserved-indent';
                    spacesSpan.setAttribute('data-spaces', whitespace.length);
                    spacesSpan.textContent = whitespace;

                    // 替换原始HTML中的空格
                    const contentWithoutIndent = originalHtml.substring(leadingWhitespaceMatch[0].length);
                    block.innerHTML = '';
                    block.appendChild(spacesSpan);

                    // 添加剩余内容
                    const contentSpan = document.createElement('span');
                    contentSpan.innerHTML = contentWithoutIndent;
                    block.appendChild(contentSpan);
                }
            });
        });
    }


    // 提取文章内容并复制到剪贴板
    function extractAndCopyArticle() {
        const articles = document.getElementsByClassName('content');

        if (articles.length === 0) {
            showStatus('未找到内容', true);
            return;
        }
        preserveCodeIndentation()

        // 创建Turndown实例
        const turndownService = new TurndownService({
            headingStyle: 'atx',
            codeBlockStyle: 'fenced',
            emDelimiter: '*'
        });

        // 增强Turndown以更好地处理代码块
        turndownService.addRule('codeBlocks', {
            filter: function (node, options) {
                return (
                    node.nodeName === 'DIV' &&
                    node.classList.contains('ql-code-block-container')
                );
            },
            replacement: function(content, node) {
                // 提取代码块内容
                let codeLines = [];
                const codeBlocks = node.querySelectorAll('.ql-code-block');

                // 检测语言
                let language = 'go'; // 假设是Go语言,可根据实际情况调整

                // 处理每一行代码
                codeBlocks.forEach(block => {
                    // 查找缩进元素
                    const indentSpan = block.querySelector('.preserved-indent');
                    let indentSpaces = 0;

                    // 如果找到缩进元素,获取缩进数量
                    if (indentSpan) {
                        indentSpaces = parseInt(indentSpan.getAttribute('data-spaces') || '0', 10);
                        // 移除缩进元素以便获取纯文本内容
                        indentSpan.remove();
                    }

                    // 获取纯文本内容
                    let textContent = block.textContent;

                    // 添加缩进和内容
                    codeLines.push(' '.repeat(indentSpaces) + textContent);
                });

                // 将所有行组合
                const codeContent = codeLines.join('\n');

                // 返回Markdown格式的代码块
                return '```' + language + '\n' + codeContent + '\n```\n\n';
            }
        });

        // 增强对图片的处理
        turndownService.addRule('images', {
            filter: 'img',
            replacement: function(content, node) {
                const alt = node.alt || '';
                let src = node.getAttribute('src') || '';

                // 处理相对URL
                if (src && !src.match(/^(https?:)?\/\//)) {
                    if (src.startsWith('/')) {
                        // 域名根路径
                        const domain = window.location.origin;
                        src = domain + src;
                    } else {
                        // 相对当前路径
                        const base = window.location.href.split('/').slice(0, -1).join('/');
                        src = base + '/' + src;
                    }
                }

                return '![' + alt + '](' + src + ')';
            }
        });

        let allMarkdown = '';
        const title = document.getElementsByClassName('title')[0];
        allMarkdown += '## ' + title.textContent + '\n\n'

        for (let i = 0; i < articles.length; i++) {
            // 创建一个新的文档片段作为工作区域
            const tempContainer = document.createElement('div');
            // 克隆节点以避免修改原始DOM
            tempContainer.appendChild(articles[i].cloneNode(true));

            // 移除其他可能不需要的元素
            const elementsToRemove = tempContainer.querySelectorAll('.comments, .related-posts, .share-buttons, nav, .navigation, .ads, script, style');
            elementsToRemove.forEach(el => el.remove());

            // 获取第一个article元素(在tempContainer中)
            const processedArticle = tempContainer.getElementsByClassName('content')[0];
            if (processedArticle) {
                // 转换为Markdown
                const markdown = turndownService.turndown(processedArticle.innerHTML);
                allMarkdown += markdown + '';
            }
        }

        if (allMarkdown) {
            // 复制到剪贴板
            GM_setClipboard(allMarkdown);
            showStatus('文章已转换为Markdown并复制');
        } else {
            showStatus('处理文章内容失败', true);
        }
    }

    // 添加快捷键(Alt+M)
    document.addEventListener('keydown', function(e) {
        if (e.altKey && e.key === 'm') {
            extractAndCopyArticle();
        }
    });

    // 初始化
    function init() {
        createCopyButton();
    }

    // 等待页面完全加载
    window.addEventListener('load', init);
})();