简单gnu(emacs)邮件列表存档优化浏览油猴脚本(类似 old reddit、hacker news风格,支持明暗主题)

// ==UserScript==
// @name         The gnu mailing list archive thread shows optimization
// @namespace    http://tampermonkey.net/
// @version      1.0
// @match        https://lists.gnu.org/archive/html/*/*/threads.html
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==

(function() {
    'use strict';

    GM_addStyle(`
        :root {
            --bg-color: white;
            --text-color: #333;
            --meta-color: #666;
            --border-color: #e0e0e0;
            --card-bg: white;
            --body-bg: #f8f9fa;
            --link-color: #dc3545;
            --child-border: #eee;
        }
        @media (prefers-color-scheme: dark) {
            :root {
                --bg-color: #1e1e1e;
                --text-color: #e0e0e0;
                --meta-color: #a0a0a0;
                --border-color: #444;
                --card-bg: #2d2d2d;
                --body-bg: #252525;
                --link-color: #ff6b6b;
                --child-border: #444;
            }
        }
        body {
            background-color: var(--bg-color);
            color: var(--text-color);
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        .thread-card {
            margin: 12px 0;
            padding: 15px;
            border: 1px solid var(--border-color);
            border-radius: 6px;
            background: var(--card-bg);
        }
        .thread-header {
            cursor: pointer;
            display: grid;
            grid-template-columns: 1fr auto;
            gap: 10px;
            align-items: center;
        }
        .thread-meta {
            color: var(--meta-color);
            font-size: 0.85em;
            margin-top: 4px;
        }
        .email-body {
            margin: 10px 0;
            padding: 10px;
            background: var(--body-bg);
            border-radius: 4px;
            white-space: pre-wrap;
            color: var(--text-color);
        }
        .original-link {
            color: var(--link-color) !important;
            font-family: monospace;
            white-space: nowrap;
            text-decoration: none !important;
            border-bottom: none !important;
        }
        .child-threads {
            margin-left: 28px;
            border-left: 2px solid var(--child-border);
        }
    `);

    const extractThreadInfo = ($li) => {
        const authorElement = $li.children('i').first();
        const dateElement = $li.children('tt').first();

        // 修复点:使用直接子元素选择器
        const $subjectLink = $li.children('b').first().find('a').first();

        return {
            subject: $subjectLink.text().trim(), // 仅获取直接子元素的标题
            author: authorElement.length ?
                authorElement.text().trim().replace(/[,$]/g, '') : '未知作者',
            date: dateElement.length ?
                dateElement.text().trim().replace(/\//g, '-') : '未知日期',
            href: $subjectLink.attr('href')
        };
    };
    async function loadEmailContent(url) {
        try {
            const response = await new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: url,
                    onload: resolve,
                    onerror: reject
                });
            });

            const parser = new DOMParser();
            const doc = parser.parseFromString(response.responseText, "text/html");
            const headers = [
                'From', 'Subject', 'Date', 'User-agent'
            ].reduce((acc, key) => {
                const $row = $(doc).find(`tr:contains('${key}:')`);
                acc[key.toLowerCase()] = $row.length ? $row.find('td').eq(1).text().trim() : 'N/A';
                return acc;
            }, {});

            const body = response.responseText
                .split('<!--X-Body-of-Message-->')[1]
                ?.split('<!--X-Body-of-Message-End-->')[0]
                ?.trim() || '内容不可用';

            return {
                ...headers,
                body: body.replace(/(\) )(.{80})/g, '$1\n$2'),
                url: url
            };
        } catch(e) {
            console.error('邮件加载失败:', e);
            return null;
        }
    }

    function createThreadElement(liNode) {
        const $li = $(liNode);
        const info = extractThreadInfo($li);
        const $card = $('<div>').addClass('thread-card');
        let isLoaded = false;

        const $header = $(`
            <div class="thread-header">
                <div>
                    <div>${info.subject}</div>
                    <div class="thread-meta">
                        ${info.author} • ${info.date}
                    </div>
                </div>
                <a href="${info.href}" class="original-link" target="_blank">🔗</a>
            </div>
        `);

        const $content = $('<div>').hide();

        // 定义展开方法
        const expand = async () => {
            if (!isLoaded) {
                const data = await loadEmailContent(info.href);
                if (data) {
                    $content.html(`
                        <div class="email-header">
                            <div><b>From:</b> ${data.from}</div>
                            <div><b>Date:</b> ${data.date}</div>
                            <div><b>Subject:</b> ${data.subject}</div>
                        </div>
                        <pre class="email-body">${data.body}</pre>
                    `);
                    isLoaded = true;
                }
            }
            $content.slideDown();
        };

        // 将展开方法附加到卡片元素
        $card.data('expand', expand);

        $header.on('click', async (e) => {
            if ($(e.target).is('a')) return;

            if (!isLoaded) {
                const data = await loadEmailContent(info.href);
                if (data) {
                    $content.html(`
                        <div class="email-header">
                            <div><b>From:</b> ${data.from}</div>
                            <div><b>Date:</b> ${data.date}</div>
                            <div><b>Subject:</b> ${data.subject}</div>
                        </div>
                        <pre class="email-body">${data.body}</pre>
                    `);
                    isLoaded = true;
                }
            }

            $content.slideToggle(async () => {
                // 展开后自动展开直接子线程
                if ($content.is(':visible')) {
                    const $childCards = $card.find('.child-threads > .thread-card');
                    $childCards.each(function() {
                        const expandFn = $(this).data('expand');
                        if (typeof expandFn === 'function') {
                            expandFn();
                        }
                    });
                }
            });
        });

        // 处理子线程
        const $children = $li.children('ul').first().children('li');
        if ($children.length > 0) {
            const $childContainer = $('<div class="child-threads">');
            $children.each(function() {
                $childContainer.append(createThreadElement(this));
            });
            $card.append($childContainer);
        }

        return $card.prepend($header, $content);
    }

    // 应用转换
    $(() => {
        const $originalList = $('body > ul').first();
        const $newView = $('<div id="reddit-view">');

        $originalList.children('li').each(function() {
            $newView.append(createThreadElement(this));
        });

        $('body').empty().css({
            'max-width': '800px',
            'margin': '0 auto',
            'padding': '20px'
        }).append($newView);
    });
})();
10 个赞

亲测可用,zsbd